Version: 4.4.0 Citadel · Language: Go · License: MIT · Platform: Linux · Windows · macOS
ForgeZero is a high-performance, zero-overhead build tool for assembly and C developers. It wraps NASM, GAS, FASM, GCC, Clang, Zig, and LD into a single unified command-line interface — no Makefiles, no build scripts, no configuration required to get started.
Inspired by the simplicity of Suckless and the efficiency of TinyCC
Non nobis, Domine, non nobis, sed nomini tuo da Gloriam
Benchmarks measured against standard nasm -f elf64 && ld and make -j4 pipelines. Test environment: Intel Core i5-10310U (4C/8T, 1.7 GHz base), Arch Linux, Samsung 980 NVMe. Results are mean ± stddev over ≥10 runs via hyperfine.
| Modules | ForgeZero (fz) |
Traditional (make -j4) |
Speedup |
|---|---|---|---|
| 20 | 19.3 ± 1.2 ms | 45.4 ± 2.3 ms | 2.35× |
| 50 | 31.1 ± 1.3 ms | 85.0 ± 2.1 ms | 2.73× |
| 100 | 57.0 ± 5.3 ms | 185.5 ± 7.7 ms | 3.25× |
| 150 | 73.1 ± 4.3 ms | 229.3 ± 3.6 ms | 3.14× |
| 200 | 82.2 ± 4.2 ms | 291.1 ± 11.2 ms | 3.54× |
| 400 | 223.1 ± 9.8 ms | 1105.0 ± 24.1 ms | 4.95× |
| Metric | fz |
make -j4 |
|---|---|---|
| Time growth (20→400 modules) | +1056% | +2333% |
| Overhead per module | ~0.36 ms | ~1.23 ms |
| I/O operations | 0 intermediate files | 2× modules (.o read/write) |
| Process forks | 1 | ~2× modules + 1 |
✅ Conclusion: ForgeZero maintains ~3–5× speedup at scale, growing to nearly 5× at 400 modules. Traditional pipelines suffer from super-linear overhead due to process spawning, I/O contention, and CPU cache thrashing. ForgeZero's single-process design preserves cache locality across the entire build.
| Factor | Traditional (make + nasm + ld) |
ForgeZero (fz) |
|---|---|---|
| Processes | 400+ forks at scale | 1 process (integrated pipeline) |
| I/O | Writes N intermediate .o files to disk |
Zero intermediate files (in-memory) |
| CPU Cache | Cold start for every fork | Hot cache (code & data stay in L1/L2) |
| Parallelism | OS-level (-j4), high scheduling overhead |
Goroutines, zero-cost concurrency |
| Memory | GC/Allocator overhead per process | Zero-allocation hot path (0 allocs/op, 0 B/op) |
| Allocations | Unbounded heap churn | Stack-buffered syscalls in hot paths |
| Modules | fz (est.) |
make -j4 (est.) |
Speedup |
|---|---|---|---|
| 20 | 19 ms | 45 ms | 2.35× |
| 100 | 57 ms | 185 ms | ~3.2× |
| 400 | 223 ms | 1105 ms | ~4.9× |
| 1000 | ~530 ms | ~3000+ ms | ~5.5× |
Note: Projections beyond 400 modules assume continued sub-linear growth. Real-world results vary based on I/O and CPU contention.
# Clone and build ForgeZero
git clone https://github.com/forgezero-cli/ForgeZero
cd ForgeZero
go build -o fz ./cmd/fz
# Run the benchmark script (generates N test modules and runs hyperfine)
./bench.sh # Edit NUM_MODULES in script for different module counts
# Export benchmark results to Markdown
hyperfine --warmup 3 --prepare 'make clean && rm -rf .fz_objs fz_out' \
'./fz -dir . -out fz_out' 'make -j4' \
--export-markdown results.md- Overview
- Requirements
- Installation
- 3.1 Linux — Debian / Ubuntu
- 3.2 Linux — Fedora / RHEL / CentOS
- 3.3 Linux — Arch Linux / Manjaro
- 3.4 Linux — openSUSE
- 3.5 macOS
- 3.6 Windows
- 3.7 Build from Source (All Platforms)
- 3.8 Go Install
- Quick Start
- Supported Languages & Extensions
- Build Modes
- 6.1 Single File Mode
- 6.2 Directory Mode
- 6.3 Configuration File Mode
- CLI Reference
- Linking Modes
- C Compilation
- 9.1 Strict Warning Flags
- 9.2 Sanitizers
- C++ Compilation
- Cross-Compilation
- Static Library Mode
- Shared Library Mode
- Package Manager (fz pm)
- Internal Mechanisms
- 15.1 Build Cache (BLAKE3)
- 15.2 Pre-link Symbol Check
- 15.3 Watch Mode
- 15.4 JSON Output
- 15.5 Clean
- 15.6 Parallel Builds
- 15.7 Interactive Shell
- 15.8 Virtual Filesystem Layer (VFS)
- Configuration File Reference
- 16.1 Basic Fields
- 16.2 Multiple Source Directories
- 16.3 Explicit Source File Lists
- 16.4 Include & Exclude Patterns
- 16.5 Library Linking
- 16.6 Custom Compiler & Linker Flags
- 16.7 .fzignore File
- 16.8 Full Annotated Example
- Assembler Backends
- 17.1 NASM (.asm)
- 17.2 GAS (.s / .S)
- 17.3 FASM (.fasm)
- Zig Toolchain Backend
- Supply Chain Security
- Reproducible Builds
- Source Tree Integrity (fz verify)
- Build Profiler (fz bench)
- WebAssembly (WASM)
- Project Initialization
- LSP & IDE Integration
- Self-Update
- Examples
- Exit Codes
- Troubleshooting
- Roadmap
- Virtual Filesystem Layer (Aegis)
- Aegis Security Core
- System Self-Audit (
fz doctor) - Cross-Platform Readiness
- Testing Standards (Aegis)
- HADES Engine: Codegen & ELF Emission
- Contributing
- License
ForgeZero removes the friction between writing assembly (or C) code and running it. Instead of managing assembler flags, linker invocations, and object file paths by hand, you point fz at a source file or directory and it handles everything:
- Detects the file type and selects the correct assembler backend automatically.
- Compiles each source file into an object file with appropriate flags.
- Checks for duplicate global symbols across all objects before linking.
- Links everything into a single binary using the most appropriate linker.
- Caches compiled objects using BLAKE3 so unchanged files are never recompiled.
- Optionally watches the filesystem and rebuilds on every save.
- Emits structured JSON build reports for CI/CD integration.
- Generates
compile_commands.jsonfor full LSP and IDE integration. - Supports cross-compilation to ARM, RISC-V, WASM, and other targets via
-target. - Builds static libraries (
.a) and shared libraries (.so/.dylib) in addition to executables. - Compiles C++ (
.cpp,.cc,.cxx) with the same strict standards as C. - Manages external C/ASM dependencies via the built-in package manager (
fz pm). - Generates CycloneDX SBOMs with BLAKE3 hashes for supply chain transparency.
- Runs a built-in SAST scanner to detect secrets, license violations, and dangerous patterns.
- Guarantees byte-identical reproducible builds across machines.
- Verifies source tree integrity via BLAKE3 manifests.
- Profiles every build phase with nanosecond precision (
fz bench). - Compiles to WebAssembly via
wasm32-emscriptenandwasm32-wasi. - Routes security-sensitive filesystem operations through a virtual layer with TOCTOU-safe
OpenVerifiedreads (v3.1.0 Aegis). - Runs
fz doctorto verify toolchain presence, directory permissions, and platform integrity before builds. - Achieves zero heap allocations on all linker hot-paths —
0 allocs/op,0 B/opin micro-benchmarks (v4.1.0 Citadel). - Uses stack-buffered syscalls (
openHot,unlinkHot) for path conversion, bypassingos.Fileandpath/filepathheap overhead. - Emits correct, deterministic ELF64 binaries with local-before-global symbol table ordering and fixed absolute relocation offsets (HADES engine, v4.1.0).
What's new in v4.1.0 Citadel:
- Zero-allocation linker hot-path —
copyFileHot,resolveSymbols, andemitRelocationsachieve0 allocs/opand0 B/opin all micro-benchmarks. GC pressure on the hot path is eliminated entirely. If you find an allocation in these paths, it is a bug. - Stack-buffered syscalls —
openHotandunlinkHotuse fixed-size stack buffers for UTF-8 path conversion, removing all transient heap objects from the kernel boundary. Sustained copy throughput reaches ~1.18 GB/s on Intel i5-10310U hardware with zero GC interference. - HADES ELF emitter refactor — local symbols now precede global symbols in
.symtab, satisfying strict linker compliance. Absolute relocation offsets forcallandjmptargets are calculated deterministically (verified via factorial execution test, exit code 120). Lexer/parser boundary checks prevent CPU instructions from being misidentified as user-defined labels. - Direct linker invocation (
-ld) — new flag bypasses compiler validation layers and invokes the linker directly, reducing orchestration overhead by ~3–5% on small projects. - Arena size override (
--arena-size=N) — overrides default arena capacity for plugin execution (advanced use). - Extended benchmark coverage — 200-module and 400-module data points added;
4.95×speedup confirmed at 400 modules. - Platform-specific syscall drivers — conditional compilation via
//go:build linux,windows,darwinbuild tags. Linux uses directSYS_OPENAT/SYS_UNLINKATviagolang.org/x/sys/unix; Windows uses nativeCreateFile/ReadFile/WriteFileviagolang.org/x/sys/windows; Darwin is stubbed with an isolated syscall layer. - Cross-architecture validation — verified builds for
amd64andarm64across all target OSes; no CGO dependencies, pure Go + static C linkage. - 100%
golangci-lintcompliance — strict configuration (-E gofmt,govet,staticcheck,unused), zero lint warnings in main branch. - Backward compatible — v4.1.0 is fully compatible with v4.0.x configurations and CLI flags. No migration required.
What's new in v3.1.0 Aegis:
- Virtual filesystem abstraction (
internal/fs) — all durable I/O routes through aFileSysteminterface with Unix and native Windows implementations.OpenVerifiedcloses the TOCTOU window between metadata check and read open viaLstat+SameFileidentity verification. - Hardened subprocess execution — external tools (
git,ar,zig,fasm,gcc,ld,nasm, …) invoke exclusively throughutils.RunCommand, which resolves binaries withexec.LookPath, validates every argument, and runs under a fixed, reproducibility-oriented environment. - Atomic secure writes — manifests, configuration files, SBOM output, and doctor probe files use
SecureWriteFile: mode0600temporary file, flush via close, platform-specific atomic rename (retry loop on Windows file locks). - Constant-time toolchain checksum comparison — optional per-tool BLAKE3 expectations in config are verified with
crypto/subtle.ConstantTimeCompareto reduce timing leakage during integrity audits. fz doctor— four-stage self-audit: toolchain reachability, recursive permission probe (read/write viaOpenVerified), platform integrity (GOOS/GOARCH, VFS backend name, execution root, CPU count), and consolidated human or--jsonreporting.- Native Windows I/O path —
//go:build windowsselectsfs.Windows,CleanPathfor drive/UNC normalization, andrenameAtomicwith bounded retries when AV software holds transient locks. - Fault-injection test suite —
fs.MockinjectsErrDiskFull,ErrPermission,ErrTimeout, and related errors; critical internal packages target 90%+ statement coverage.
What's new in v3.0.0 GLORIA:
- Zig toolchain backend —
zig cc/zig c++as primary or alternative backend for C/C++. Zero external dependencies for cross-compilation: Zig ships all headers, libc, and sysroots internally. - SBOM (CycloneDX + BLAKE3) —
fz sbomgenerates a Software Bill of Materials in CycloneDX format with BLAKE3 hashes for every component. - SAST audit (
fz audit) — built-in security scanner: hardcoded secrets, license compliance (MPL/GPL detection), and dangerous C patterns. - Reproducible builds (
--reproducible) — suppression of build IDs, timestamps, and non-deterministic path references; object files sorted before linking for byte-identical output. - Source tree verification (
fz verify) — generates and checks BLAKE3 manifests of the entire source tree. - Symlink boundary protection — every symlink is resolved and validated against the project root, blocking symlink race attacks.
fz bench— nanosecond-precision build profiler. Multi-run averaging and JSON output supported.- Race-free parallel pipeline — verified with
go test -raceacross the complete test suite. - FASM improvements — automatic
format ELF64injection and correct-dDEBUG=1/ debug symbol pass-through. - WebAssembly —
wasm32-emscriptenandwasm32-wasitargets.
What's new in v2.0.0 NEXUS:
- BLAKE3 hashing — cache is up to 7× faster. 10 MB file hash time dropped from ~58 ms to ~8.7 ms.
- Package manager (
fz pm) — manage external C/ASM dependencies from Git or the official catalog. - Shared library support —
-shared,-cc-flag, and-ld-flagflags for.so,.dylib, and.dlltargets. - High test coverage — utils 84%, linker 60%, assembler 60%, builder 56%.
What's new in v1.9.0:
- Cross-compilation —
-target <triple>supports ARM, RISC-V, x86_64, and any standard GNU cross-compilation triple. - LSP support —
-compile-commandsgeneratescompile_commands.jsonfor clangd, ccls, and LSP-aware editors. - Smart self-update —
fz -updatebacks up the old binary to/usr/local/bin/fz.oldbefore replacing it.
What's new in v1.8.0:
- Static libraries —
-type staticand-libbuild.aarchives viaar. - Unique object file names — multi-directory projects no longer produce colliding
.onames.
What's new in v1.7.0:
- Parallel builds —
-j Ncompiles all source files concurrently (0 = auto). - Interactive shell —
fz -shellopens a REPL for runningfzcommands without re-invoking the binary. - C++ support —
.cpp,.cc,.cxxcompiled withg++orclang++.
What's new in v1.6.0:
- Project initialization —
fz -initscaffolds.fz.yaml,.fzignore, andREADME.md. - Flat binary output —
-format binfor bootloaders, firmware, and embedded targets.
What's new in v1.5.0:
- Multiple source directories —
source_dirsscanned in parallel. - Explicit source file lists —
source_filesbypasses directory scanning entirely. .fzignorefile —.gitignore-style exclusion rules for recursive scanning.- Multi-level config merging — system, user, and project YAML configs merged in priority order.
ForgeZero is intentionally lightweight — a single statically compiled Go binary with no runtime dependencies beyond the standard assembler/compiler toolchain.
| Source type | Required tool | Notes |
|---|---|---|
.asm |
nasm |
x86/x86-64 Intel syntax |
.s / .S |
gcc (drives as) |
AT&T syntax; .S files are C-preprocessed first |
.fasm |
fasm |
Must be downloaded separately from flatassembler.net |
.c |
gcc or clang |
Strict flags + sanitizers by default |
.cpp / .cc / .cxx |
g++ or clang++ |
Same strict flags as C; clang++ preferred in strict mode |
| Linker | Required for |
|---|---|
gcc |
Default linking, C runtime support |
ld |
Raw linking (-mode raw), linker scripts; direct invocation with -ld flag (v4.1.0) |
clang |
Strict sanitizer mode (-strict) |
ar |
Static library mode (-type static) |
When using -target <triple>, fz looks for prefixed toolchain binaries on your PATH:
| Target triple | Expected compiler prefix |
|---|---|
arm-linux-gnueabihf |
arm-linux-gnueabihf-gcc |
aarch64-linux-gnu |
aarch64-linux-gnu-gcc |
riscv64-linux-gnu |
riscv64-linux-gnu-gcc |
x86_64-linux-gnu |
x86_64-linux-gnu-gcc |
Install cross-compilers via your package manager (e.g. sudo apt install gcc-arm-linux-gnueabihf).
When using -zig, no prefixed toolchain is required — Zig resolves the target internally. See Section 18.
| Tool | Purpose |
|---|---|
nm |
Pre-link duplicate symbol check (primary) |
objdump |
Fallback for symbol check |
readelf |
Second fallback for symbol check |
git |
Required for fz pm add |
zig |
Required for -zig backend (v3.0.0+) |
emcc |
Required for wasm32-emscripten target (v3.0.0+) |
hyperfine |
Benchmarking (fz bench and bench.sh) — optional but recommended |
Go 1.21 or later is required to build fz from source.
Install system dependencies:
sudo apt update
sudo apt install -y nasm gcc binutils gitInstall Clang (optional, for -strict mode):
sudo apt install -y clangInstall cross-compilation toolchain (optional):
sudo apt install -y gcc-arm-linux-gnueabihf
sudo apt install -y gcc-aarch64-linux-gnu
sudo apt install -y gcc-riscv64-linux-gnuInstall Zig (optional, for -zig backend):
wget https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz
tar -xf zig-linux-x86_64-0.13.0.tar.xz
sudo mv zig-linux-x86_64-0.13.0 /opt/zig
echo 'export PATH="$PATH:/opt/zig"' >> ~/.bashrc
source ~/.bashrc
zig versionInstall FASM (optional, for .fasm files):
wget https://flatassembler.net/fasm-1.73.32.tgz
tar -xzf fasm-1.73.32.tgz
sudo cp fasm/fasm /usr/local/bin/
chmod +x /usr/local/bin/fasmInstall ForgeZero via Go:
go install github.com/forgezero-cli/ForgeZero/cmd/fz@latestEnsure ~/go/bin is on your PATH:
echo 'export PATH="$PATH:$(go env GOPATH)/bin"' >> ~/.bashrc
source ~/.bashrcVerify:
fz -vInstall system dependencies:
# Fedora
sudo dnf install -y nasm gcc binutils clang git
# RHEL / CentOS — enable EPEL first for nasm
sudo dnf install -y epel-release
sudo dnf install -y nasm gcc binutils clang gitInstall Zig (optional):
wget https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz
tar -xf zig-linux-x86_64-0.13.0.tar.xz
sudo mv zig-linux-x86_64-0.13.0 /opt/zig
echo 'export PATH="$PATH:/opt/zig"' >> ~/.bashrcInstall ForgeZero:
go install github.com/forgezero-cli/ForgeZero/cmd/fz@latestInstall system dependencies:
sudo pacman -S --noconfirm nasm gcc binutils clang git zig
# FASM is available in the AUR
yay -S fasmInstall ForgeZero:
go install github.com/forgezero-cli/ForgeZero/cmd/fz@latestInstall system dependencies:
sudo zypper install -y nasm gcc binutils clang gitInstall ForgeZero:
go install github.com/forgezero-cli/ForgeZero/cmd/fz@latestmacOS support is in progress. The following setup works for most use cases today.
Install Homebrew (if not already installed):
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Install dependencies:
brew install nasm gcc go git zigNote: macOS ships
clangas the system compiler under thegccalias via Xcode Command Line Tools.brew install gccinstalls it asgcc-14(or similar). ForgeZero uses whatevergccresolves to on yourPATH. The Darwin syscall layer is currently stubbed — builds succeed but runtime behavior is untested.
Install ForgeZero:
go install github.com/forgezero-cli/ForgeZero/cmd/fz@latestAdd Go's bin directory to your shell profile:
echo 'export PATH="$PATH:$(go env GOPATH)/bin"' >> ~/.zshrc
source ~/.zshrcVerify:
fz -vAs of v3.1.0 Aegis, ForgeZero ships a first-class native Windows filesystem backend (internal/fs.Windows) and Windows-specific atomic rename retry logic. As of v4.1.0 Citadel, platform-specific syscall drivers are implemented via golang.org/x/sys/windows (CreateFile, ReadFile, WriteFile), making native Windows builds a fully supported compilation target.
WSL2 remains the path of least resistance for full toolchain parity on day-to-day development, but native Windows I/O no longer depends on a Linux compatibility shim.
- Open PowerShell as Administrator and run:
wsl --install-
Restart your machine. Open Ubuntu from the Start menu.
-
Inside the WSL2 terminal, follow the Debian/Ubuntu instructions.
-
Access your Windows files from WSL2 at
/mnt/c/Users/<YourName>/.
Native Windows support requires manual toolchain setup via MSYS2.
Step 1 — Install MSYS2:
Download and run the installer from msys2.org. After installation, open the MSYS2 MinGW 64-bit terminal and run:
pacman -Syu
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-binutils mingw-w64-x86_64-clang gitStep 2 — Install NASM for Windows:
Download the Windows installer from nasm.us. Run it and note the installation path (e.g. C:\Program Files\NASM).
Step 3 — Add tools to your PATH:
Open System Properties → Advanced → Environment Variables. Add the following to the Path variable:
C:\msys64\mingw64\bin
C:\Program Files\NASM
C:\Users\<YourName>\go\bin
Step 4 — Install Go for Windows:
Download from go.dev/dl and run the installer.
Step 5 — Install ForgeZero:
Open Command Prompt or PowerShell:
go install github.com/forgezero-cli/ForgeZero/cmd/fz@latestOr build from source:
git clone https://github.com/forgezero-cli/ForgeZero.git
cd ForgeZero
go build -o fz.exe ./cmd/fz/main.goMove fz.exe to a directory on your PATH.
Known limitation:
-sanitizeand-strictrequire Clang with AddressSanitizer support compiled for Windows, available via the official LLVM Windows release but requiring additional setup beyond MSYS2. Basic NASM assembly and GCC linking work without any extra configuration.
Run fz doctor as the first command after installation to confirm PATH and directory permissions are correctly configured.
git clone https://github.com/forgezero-cli/ForgeZero.git
cd ForgeZero
go build -o fz ./cmd/fz/main.go # Linux / macOS
go build -o fz.exe ./cmd/fz/main.go # WindowsRun tests:
go test ./...
go test ./internal/... -coverInstall to PATH:
# Linux / macOS
sudo mv fz /usr/local/bin/
# Windows (PowerShell, as Administrator)
Move-Item fz.exe C:\Windows\System32\fz.exeThe simplest method if Go is already configured:
go install github.com/forgezero-cli/ForgeZero/cmd/fz@latestThe binary lands in $GOPATH/bin. Verify:
fz -vAssemble a NASM file:
fz -asm hello.asm
./helloCompile a C file:
fz -cc main.c
./mainCompile a C++ file:
fz -cc main.cpp
./mainBuild an entire directory:
fz -dir ./src
./srcInitialize a new project:
fz -initBuild with cross-compilation:
fz -cc main.c -target arm-linux-gnueabihfBuild with Zig backend (no extra toolchain needed):
fz -cc main.c -zig -target aarch64-linux-muslDirect linker invocation (v4.1.0 — bypasses compiler validation):
fz -asm boot.asm -ld -out boot.elfGenerate LSP compilation database:
fz -compile-commandsBuild a static library:
fz -dir ./src -type static -lib mylibBuild a shared library:
fz -cc mylib.c -shared -o libmylib.soAdd a package dependency:
fz pm add github.com/me/my-libGenerate a Software Bill of Materials:
fz sbomRun the security audit:
fz auditReproducible build:
fz -dir ./src --reproducibleVerify source tree integrity:
fz verifyProfile the build:
fz benchBuild for WebAssembly (WASI, via Zig):
fz -cc main.c -zig -target wasm32-wasi -out main.wasmBuild multiple directories (v1.5.0):
# .fz.yaml
source_dirs:
- kernel
- libc
- drivers
output: myosfz| Extension | Language | Backend | Notes |
|---|---|---|---|
.asm |
Assembly | NASM | x86/x86-64, Intel syntax, ELF64 |
.s |
Assembly | GAS via gcc -c |
AT&T syntax |
.S |
Assembly | GAS via gcc -c |
AT&T syntax + C preprocessor |
.fasm |
Assembly | FASM | Separate install; auto format ELF64 injection (v3.0.0) |
.c |
C | GCC, Clang, or zig cc |
Strict flags + sanitizers by default |
.cpp / .cc / .cxx |
C++ | G++, Clang++, or zig c++ |
Same strict flags as C (v1.7.0+) |
All other extensions are silently ignored during directory and recursive scanning.
Compiles and links a single source file into a binary.
fz -asm program.asm
fz -cc main.c
fz -cc main.cpp- Output binary name is derived from the source filename (
program.asm→program). - A single object file is created and removed after linking unless
-keep-objis set. - Override the binary name with
-outand the object file name with-out-obj.
Recursively scans a directory for all supported source files, compiles each to a uniquely named object file, then links everything into a single binary.
fz -dir ./srcObject file naming — names are derived from the full relative path to prevent collisions across subdirectories:
| Source file | Object file |
|---|---|
src/hello.asm |
src_hello_asm.o |
src/hello.s |
src_hello_s.o |
src/sub/hello.asm |
src_sub_hello_asm.o |
lib/hello.asm |
lib_hello_asm.o |
Object files live in .fz_objs/ and are removed after linking unless -keep-obj is passed.
ForgeZero automatically searches the working directory for a config file in this order:
.fz.yamlfz.yaml.fz.ymlfz.yml
Run without any flags to use the config:
fzUse -config to specify a path explicitly:
fz -config ./configs/release.yamlCLI flags always take precedence over config file values.
Config merging (v1.5.0): Three-level merge in priority order:
- System-level:
/etc/fz/fz.yaml - User-level:
~/.config/fz/fz.yaml - Project-level:
.fz.yamlin the working directory - CLI flags (highest priority)
fz [options]
At least one of -asm, -cc, -dir, -init, -shell, pm, sbom, audit, verify, bench, doctor, or a valid config file must be present.
| Flag | Argument | Default | Description |
|---|---|---|---|
-asm |
<file> |
— | Assemble the given assembly source file. |
-cc |
<file> |
— | Compile the given C or C++ source file. |
-dir |
<dir> |
— | Recursively build all supported files in the directory. |
-out |
<name> |
Derived from source | Name of the output binary. |
-out-obj |
<name> |
<basename>.o |
Object file name (single-file mode only). |
-mode |
auto|c|raw |
auto |
Linking mode. See Linking Modes. |
-ld |
— | off | Invoke the linker directly, bypassing compiler validation layers. Reduces overhead ~3–5% on small projects. (v4.1.0) |
-format |
elf32|elf64|bin |
elf64 |
Output format for assembled binaries. |
-target |
<triple> |
— | Cross-compilation target triple (e.g. arm-linux-gnueabihf, wasm32-wasi). |
-zig |
— | off | Use Zig (zig cc / zig c++) as the compiler backend. |
--reproducible |
— | off | Enable deterministic builds: suppress build IDs, timestamps, path references; sort objects. |
--arena-size=N |
<bytes> |
auto | Override default arena capacity for plugin execution. Advanced use only. (v4.1.0) |
-type |
executable|static |
executable |
Output type: linked binary or static library (.a). |
-lib |
<name> |
— | Output library name when -type static is used (without lib prefix or .a suffix). |
-shared |
— | off | Build a shared library (.so / .dylib / .dll). |
-cc-flag |
<flags> |
— | Extra compiler flags, space-separated, injected after standard flags. |
-ld-flag |
<flags> |
— | Extra linker flags, space-separated, appended to the linker command. |
-j |
<N> |
1 |
Parallel compilation jobs. 0 = auto (number of CPU cores). |
-T |
<script> |
— | Linker script to pass to ld. |
-Ttext |
<addr> |
— | Entry point address to pass to the linker (hex or decimal). |
-debug |
— | off | Pass -g to the assembler/compiler to emit debug symbols. |
-verbose |
— | off | Print each external command to stdout before running it. |
-keep-obj |
— | off | Preserve object files after linking (directory mode). |
-no-cache |
— | off | Disable the build cache; always recompile every file. |
-no-symbol-check |
— | off | Skip the pre-link duplicate symbol check. |
-sanitize |
— | on | Enable -fsanitize=address,undefined for C/C++. Disable with -sanitize=false. |
-strict |
— | off | Stricter sanitizers + use-after-return/scope checks. Prefers clang/clang++. |
-json |
— | off | Suppress normal output; emit a JSON build report to stdout. |
-watch |
— | off | Watch source files for changes and rebuild automatically. |
-clean |
— | off | Remove all build artifacts and exit. |
-compile-commands |
— | off | Generate compile_commands.json for LSP/IDE integration. |
-init |
— | off | Scaffold a new project: creates .fz.yaml, .fzignore, and README.md. |
-shell |
— | off | Open interactive REPL shell. |
-update |
— | off | Download and install the latest fz binary; backs up current binary to fz.old. |
-config |
<file> |
auto-detect | Path to a YAML configuration file. |
-timeout |
<sec> |
60 |
Timeout in seconds for each sub-command. |
-manifest |
<file> |
.fz.manifest |
Path to the BLAKE3 source manifest used by fz verify. |
-h, --help |
— | — | Print help and exit. |
-v, --version |
— | — | Print version and exit. |
| Command | Description |
|---|---|
fz pm add <repo>[@version] |
Clone a package from a Git repository and register it in .fz.yaml. |
fz pm remove <package> |
Remove a package and clean up .fz.yaml and empty parent directories. |
fz pm list |
List all installed packages. |
fz pm update |
Update all installed packages to the latest commit or tag. |
fz pm catalog |
Browse the official ForgeZero package catalog. |
fz pm search <query> |
Search the catalog by name or keyword. |
fz pm install <name> |
Install a package from the catalog with BLAKE3 hash verification. |
| Command | Description |
|---|---|
fz sbom |
Generate a CycloneDX SBOM with BLAKE3 hashes for all build components. |
fz sbom -o <path> |
Write the SBOM to a specific output file (default: sbom.cdx.json). |
fz sbom -dir <dir> |
Generate SBOM scoped to a specific source directory. |
fz audit |
Run the built-in SAST scanner: secrets, license compliance, and dangerous patterns. |
fz audit -dir <dir> |
Audit a specific directory. |
fz audit -json |
Emit the audit report as JSON to stdout. |
fz verify |
Verify the current source tree against the stored BLAKE3 manifest. |
fz verify --generate |
Generate a new BLAKE3 manifest of the current source tree. |
fz verify --strict |
Also report UNTRACKED files not present in the manifest. |
fz verify -manifest <f> |
Use a specific manifest file instead of the default .fz.manifest. |
fz doctor |
Run the Aegis self-audit: toolchain, permissions, platform integrity. |
fz doctor -root <dir> |
Audit a specific project root (default: current working directory). |
fz doctor -json |
Emit the audit report as JSON; exit code 1 if healthy is false. |
| Command | Description |
|---|---|
fz bench |
Profile the build: nanosecond-precision timing for every phase. |
fz bench -n <N> |
Run N times; report average and standard deviation per phase. |
fz bench -json |
Emit the benchmark report as JSON. |
The -mode flag controls how compiled object files are linked into a final binary.
ForgeZero tries linkers in sequence until one succeeds:
gcc— standard linking with libc and C runtime.gcc -no-pie— position-dependent executable; needed when code assumes fixed load addresses.ld— raw system linker; last resort.
When -strict is active, clang with full sanitizer flags is tried first.
fz -asm program.asm -mode c
fz -cc main.c -mode cAlways links using gcc (or clang in strict mode). Required when code calls libc functions (printf, malloc, exit, etc.) or depends on C runtime initialization.
fz -asm kernel.asm -mode raw -out kernel.binBypasses GCC entirely and invokes ld directly. Suitable for:
- OS kernels and bootloaders
- Bare-metal firmware
- Programs that define their own
_startand use raw syscalls - Embedded targets requiring full control over binary layout
Warning: Raw-linked binaries cannot reference any libc symbol.
fz -asm boot.asm -ld -out boot.elfThe -ld flag invokes the linker directly, bypassing compiler validation layers entirely. This reduces orchestration overhead by ~3–5% on small projects. Unlike -mode raw, -ld does not imply a particular linking strategy — it simply removes the gcc/clang wrapper from the invocation chain. Use this when you need the tightest possible control over linker arguments and accept full responsibility for the link step.
Every .c file compiled by fz receives these flags unconditionally:
-Wall -Wextra -Werror -Wpedantic -Wshadow -Wconversion
| Flag | Effect |
|---|---|
-Wall |
Enables most common warnings |
-Wextra |
Enables additional warnings beyond -Wall |
-Werror |
Promotes all warnings to errors |
-Wpedantic |
Enforces strict ISO C compliance |
-Wshadow |
Warns when a local variable shadows an outer one |
-Wconversion |
Warns on implicit type conversions that may lose precision |
Any warning is treated as an error and stops the build immediately. This is intentional — ForgeZero enforces clean, portable C code by default.
Standard mode (default — always enabled unless -sanitize=false):
-fsanitize=address
-fsanitize=undefined
Strict mode (-strict):
-fsanitize=address
-fsanitize=undefined
-fsanitize-address-use-after-return=always (Clang only)
-fsanitize-address-use-after-scope
Disable sanitizers (release build / benchmarking):
fz -cc main.c -sanitize=falseNote: Sanitizers are automatically disabled for WebAssembly targets (
wasm32-*). See Section 23.
Added in v1.7.0. ForgeZero compiles .cpp, .cc, and .cxx files using g++ or clang++ (or zig c++ when -zig is active). The same strict warning flags applied to C are applied identically to C++:
-Wall -Wextra -Werror -Wpedantic -Wshadow -Wconversion
Sanitizers are also enabled by default for C++ in the same way as C.
Single C++ file:
fz -cc main.cpp
fz -cc main.cc
fz -cc main.cxxMixed C and C++ project directory:
fz -dir ./srcfz dispatches .c files to gcc/clang/zig cc and .cpp/.cc/.cxx files to g++/clang++/zig c++ automatically. All objects are linked in a single step.
Added in v1.9.0, extended with Zig backend in v3.0.0, validated for amd64 and arm64 in v4.1.0.
fz -cc main.c -target arm-linux-gnueabihf
fz -cc main.c -target aarch64-linux-gnu
fz -cc main.c -target riscv64-linux-gnu
fz -dir ./src -target arm-linux-gnueabihf -out firmware
# With Zig backend — no cross-compiler package required
fz -cc main.c -zig -target aarch64-linux-musl
fz -cc main.c -zig -target riscv64-linux-muslWithout -zig, fz constructs prefixed compiler and linker names by prepending the triple:
- Compiler:
<triple>-gcc - C++ compiler:
<triple>-g++ - Linker:
<triple>-gccor<triple>-lddepending on linking mode - Archiver:
<triple>-ar(when-type static)
With -zig, the target triple is passed directly to zig cc -target. No prefixed binary lookup happens.
# Debian / Ubuntu
sudo apt install gcc-arm-linux-gnueabihf
sudo apt install gcc-aarch64-linux-gnu
sudo apt install gcc-riscv64-linux-gnu
# Fedora
sudo dnf install gcc-arm-linux-gnu
sudo dnf install gcc-aarch64-linux-gnu
# Arch Linux
sudo pacman -S arm-linux-gnueabihf-gcc
sudo pacman -S aarch64-linux-gnu-gcc# .fz.yaml
source_dirs:
- src
output: firmware.elf
target: arm-linux-gnueabihf
mode: raw
flags:
cc:
- -mcpu=cortex-m4
- -mfpu=fpv4-sp-d16
- -mfloat-abi=hard
- -ffreestanding
ld:
- -T
- linker.ldAdded in v1.8.0. ForgeZero can produce static libraries (.a archives) instead of linked executables.
fz -dir ./src -type static -lib mylibThis compiles all source files in ./src/ into object files, then archives them into libmylib.a using ar.
| Flag | Description |
|---|---|
-type static |
Build a static library instead of an executable |
-lib <name> |
Name of the library (without lib prefix and .a suffix) |
The output file is always named lib<name>.a.
Config file:
source_dirs:
- src
type: static
lib: mylibCross-compilation with static library:
fz -dir ./src -type static -lib mylib -target arm-linux-gnueabihf
fz -dir ./src -type static -lib mylib -zig -target aarch64-linux-muslAdded in v2.0.0 NEXUS.
fz -cc mylib.c -shared -o libmylib.so
fz -cc mylib.c -shared -cc-flag "-O2 -fPIC" -ld-flag "-pthread" -o libmylib.so| Flag | Description |
|---|---|
-shared |
Build a shared library instead of an executable |
-cc-flag "<flags>" |
Extra compiler flags injected after standard flags |
-ld-flag "<flags>" |
Extra linker flags appended to the linker command |
When building shared libraries for Linux, always include -fPIC in -cc-flag.
Added in v2.0.0 NEXUS. fz pm manages external C/ASM dependencies from Git repositories or the official ForgeZero package catalog.
fz pm add github.com/me/my-lib
fz pm add github.com/me/my-lib@v1.2.3Clones the repository into vendor/, checks out the version tag if given, and updates .fz.yaml (source_dirs). All subsequent fz builds include the vendored package.
fz pm remove my-libDeletes the package directory from vendor/, cleans up .fz.yaml, and removes empty parent directories.
fz pm listLists all currently installed packages and their versions.
fz pm updatePulls the latest changes for all installed packages.
fz pm catalogFetches and displays the full list of packages from the official ForgeZero catalog (https://raw.githubusercontent.com/forgezero-cli/catalog/main/catalog.json).
fz pm search iot
fz pm search cryptoSearches the official catalog by name or keyword.
fz pm install esp-idfInstalls a named package from the catalog with BLAKE3 hash verification.
How it works: packages are cloned into vendor/<package-name>/. .fz.yaml is updated automatically. All network and git operations run with configurable timeouts (default: 60 s, override with -timeout).
ForgeZero caches compiled object files in .fz_cache/ to skip recompilation of unchanged sources.
Cache key computed from:
- BLAKE3 hash of the source file contents
-debugflag state-modevalue-targetvalue
| File size | SHA256 (pre-2.0.0) | BLAKE3 (2.0.0+) |
|---|---|---|
| 10 MB | ~58 ms | ~8.7 ms |
Disable caching:
fz -dir ./src -no-cacheClear cache:
fz -dir . -cleanBefore invoking the linker, fz scans all compiled object files for duplicate global symbol definitions.
Tools used (in order of preference): nm → objdump → readelf
If a conflict is found, fz reports which files define the duplicate symbol and exits with code 1.
Disable the check:
fz -dir ./src -no-symbol-checkfz -dir ./src -watch
fz -asm main.asm -watchUses fsnotify for cross-platform filesystem events. Rebuilds are debounced with a 500 ms delay.
When -json is passed, a single JSON object is written to stdout on completion:
{
"status": "success",
"exit_code": 0,
"duration_ms": 342,
"binary": "./src",
"source_files": ["src/main.asm", "src/utils.asm"],
"object_files": ["src_main_asm.o", "src_utils_asm.o"],
"error": null
}CI/CD integration:
result=$(fz -dir ./src -json)
status=$(echo "$result" | jq -r '.status')
duration=$(echo "$result" | jq -r '.duration_ms')
if [ "$status" != "success" ]; then
echo "Build failed after ${duration}ms: $(echo "$result" | jq -r '.error')"
exit 1
fi
echo "Build succeeded in ${duration}ms"fz -dir . -cleanRemoves: .fz_objs/, .fz_cache/, all .o files, and executable files identified by the +x permission bit.
Caution: Avoid running
-cleanin directories containing pre-built third-party binaries you did not intend to delete.
fz -dir ./src -j 4 # compile up to 4 files simultaneously
fz -dir ./src -j 0 # auto: use all available CPU coresAs of v3.0.0 GLORIA, the parallel build and logging pipeline is race-condition-free, verified with go test -race.
fz -shellOpens a REPL for issuing fz commands without re-invoking the binary.
| Command | Description |
|---|---|
build <file> |
Compile and link a single source file |
build -dir <dir> |
Build all files in a directory |
set <flag> <value> |
Set a build flag for subsequent commands |
clean |
Remove build artifacts |
help |
List available commands |
exit |
Exit the shell |
New in v3.1.0 Aegis
All durable and security-sensitive I/O routes through the internal/fs FileSystem interface. Production code never calls os.Open, os.WriteFile, or os.Rename directly on security-sensitive paths.
| Component | Role |
|---|---|
FileSystem interface |
Contract for mkdir, read, write, verified open, temp files, rename, stat, symlinks |
fs.Unix |
POSIX implementation (//go:build !windows) |
fs.Windows |
Native Windows implementation (//go:build windows) |
fs.Mock |
Test double that injects per-operation errors |
utils.SetFileSystem |
Runtime swap used only in tests |
Full detail: Section 31.
| Field | Type | Default | Description |
|---|---|---|---|
source_dir |
string | — | Single source directory (backward compatible) |
source_dirs |
[]string |
— | Multiple source directories, each scanned recursively |
source_files |
[]string |
— | Exact list of files to build; overrides source_dirs |
output |
string | auto | Output binary name |
mode |
string | auto |
Linking mode: auto, c, or raw |
format |
string | elf64 |
Output format: elf32, elf64, or bin |
target |
string | — | Cross-compilation target triple |
backend |
string | auto |
Compiler backend: auto, gcc, clang, or zig |
reproducible |
bool | false |
Enable reproducible (deterministic) build mode |
type |
string | executable |
Output type: executable or static |
lib |
string | — | Library name for -type static |
jobs |
int | 1 |
Parallel compilation jobs (0 = auto) |
debug |
bool | false |
Emit debug symbols (-g) |
verbose |
bool | false |
Print all invoked commands |
keep_obj |
bool | false |
Preserve object files after linking |
no_cache |
bool | false |
Disable build cache |
sanitize |
bool | true |
Enable ASan + UBSan for C/C++ |
strict |
bool | false |
Strict sanitizer mode, prefers clang/clang++ |
ignore_file |
string | .fzignore |
Path to a .gitignore-style exclusion file |
source_dirs:
- kernel
- libc
- drivers
output: forgeos.elf
mode: rawObject file names are prefixed with their parent directory:
| Source file | Object file |
|---|---|
kernel/boot.asm |
kernel_boot_asm.o |
libc/string.c |
libc_string_c.o |
drivers/uart.c |
drivers_uart_c.o |
source_files:
- boot/start.asm
- kernel/main.c
- kernel/irq.c
output: kernel.elf
mode: rawsource_files takes precedence over source_dirs and source_dir. Each path is verified at startup.
exclude:
- "test_*"
- "*/legacy/"
- "*.tmp"
include:
- "*.asm"
- "*.c"Evaluation order: exclude → .fzignore → symlink boundary check → include → supported extensions.
libs:
- m # -lm (math)
- pthread # -lpthread
- c # -lcflags:
asm:
- -DDEBUG_BUILD
- -I./include
cc:
- -O3
- -march=native
- -DNDEBUG
- -ffreestanding
ld:
- -T
- linker.ld
- -Map
- output.mapWorks exactly like .gitignore. Evaluated after exclude patterns.
*.o
*.swp
temp/
test_*/
vendor/
legacy/old_abi.asm
# fz.yaml
source_dirs:
- kernel
- libc
- drivers
output: forgeos.elf
format: elf64
# target: arm-linux-gnueabihf
# backend: zig
# reproducible: true
mode: raw
debug: true
verbose: false
keep_obj: true
no_cache: false
jobs: 0
sanitize: true
strict: false
exclude:
- "test_*"
- "*/legacy/"
- "*.tmp"
include:
- "*.asm"
- "*.c"
- "*.cpp"
- "*.s"
libs:
- gcc
- m
flags:
asm:
- -DDEBUG_BUILD
- -I./include
cc:
- -O2
- -march=native
- -ffreestanding
ld:
- -T
- linker.ld
ignore_file: .myfzignoreCommand: nasm -felf64 <file> -o <output.o>
; hello.asm — print "Hello, World!" to stdout and exit
section .data
msg db "Hello, World!", 0x0a
len equ $ - msg
section .text
global _start
_start:
mov rax, 1
mov rdi, 1
mov rsi, msg
mov rdx, len
syscall
mov rax, 60
xor rdi, rdi
syscallfz -asm hello.asm
./helloCommand: gcc -c <file> -o <output.o>
# hello.s — AT&T syntax
.section .data
msg: .ascii "Hello, World!\n"
len = . - msg
.section .text
.global _start
_start:
movq $1, %rax
movq $1, %rdi
movq $msg, %rsi
movq $len, %rdx
syscall
movq $60, %rax
xorq %rdi, %rdi
syscallfz -asm hello.s
./helloCommand: fasm <file> <output.o>
As of v3.0.0 GLORIA, ForgeZero automatically injects format ELF64 executable when no format directive is found, and passes -dDEBUG=1 when -debug is active.
; hello.fasm — format directive can be omitted for ELF64 targets (v3.0.0+)
entry _start
segment readable writeable
msg db "Hello, World!", 10
len = $ - msg
segment readable executable
_start:
mov rax, 1
mov rdi, 1
mov rsi, msg
mov rdx, len
syscall
mov rax, 60
xor rdi, rdi
syscallfz -asm hello.fasm
./helloNew in v3.0.0 GLORIA · cross-architecture validated in v4.1.0 Citadel
The Zig compiler ships as a single self-contained binary that includes: a full copy of musl libc, glibc stubs, and WASI sysroot for every supported target; all necessary C and C++ runtime headers; a Clang-based C/C++ frontend. This makes ForgeZero genuinely zero-dependency for cross-compilation when the Zig backend is active.
fz -cc main.c -zig
fz -dir ./src -zig
fz -cc main.c -zig -target aarch64-linux-musl
fz -cc main.c -zig -target riscv64-linux-musl
fz -cc main.c -zig -target x86_64-windows-gnu
fz -dir ./src -type static -lib mylib -zig -target aarch64-linux-muslIn .fz.yaml:
source_dirs:
- src
output: myapp
backend: zig
target: aarch64-linux-musl
sanitize: false| Flags active | C compiler | C++ compiler |
|---|---|---|
| (default) | gcc or clang |
g++ or clang++ |
-zig |
zig cc |
zig c++ |
-zig -target <T> |
zig cc -target T |
zig c++ -target T |
-zig -strict |
zig cc (full sanitizers) |
zig c++ |
# Debian / Ubuntu
wget https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz
tar -xf zig-linux-x86_64-0.13.0.tar.xz
sudo mv zig-linux-x86_64-0.13.0 /opt/zig
echo 'export PATH="$PATH:/opt/zig"' >> ~/.bashrc
source ~/.bashrc
# Arch Linux
sudo pacman -S zig
# macOS
brew install zig
# Verify
zig versionAutomatic format ELF64 injection. When ForgeZero compiles a .fasm file and the file does not begin with a format directive, it automatically prepends format ELF64 executable in a temporary preprocessed copy. The original source file is never modified.
Debug flag pass-through. When -debug is active, -dDEBUG=1 is injected into the FASM command line and DWARF debug information is emitted.
if defined DEBUG
; print register state, extra checks
end iffz -asm boot.fasm -debug
gdb ./boot
(gdb) break _start
(gdb) runNew in v3.0.0 GLORIA · atomic SBOM generation hardened in v4.1.0 Citadel
fz sbom
fz sbom -o /tmp/myproject-sbom.cdx.json
fz sbom -dir ./src -o release-sbom.cdx.jsonfz sbom generates a CycloneDX SBOM (valid JSON, importable into Dependency-Track, Grype, Syft). As of v4.1.0, SBOM artifacts are produced atomically via internal helpers, preventing partial output files on crash or interrupt.
Each component entry includes: name, version (Git commit SHA for vendored packages), BLAKE3 hash, type (source-file, vendored-package, system-library), and SPDX license identifier.
fz audit
fz audit -dir ./src
fz audit -jsonThree classes of checks:
Check 1 — Hardcoded Secrets. Entropy-weighted regex patterns detect AWS keys, GitHub tokens, Slack tokens, generic key/secret/password/token assignments, PEM private key blocks, and plaintext connection strings. Findings include file, line, pattern type, and severity — never the actual secret value.
Check 2 — License Compliance.
| License | Concern | Severity |
|---|---|---|
| MPL-2.0 | Modified files must be released under MPL | WARNING |
| GPL-2.0 / GPL-3.0 | Strong copyleft | HIGH |
| AGPL-3.0 | Network copyleft | HIGH |
Proprietary in vendor/ |
May prohibit redistribution | CRITICAL |
Check 3 — Dangerous Patterns. Detects gets(), sprintf() without bounds, alloca() inside loops, format string vulnerabilities, unchecked malloc() / realloc(), chmod 777, and curl | sh patterns.
JSON output:
{
"status": "findings",
"total": 3,
"findings": [
{
"type": "hardcoded_secret",
"severity": "CRITICAL",
"file": "src/config.c",
"line": 42,
"pattern": "AWS_SECRET_ACCESS_KEY assignment",
"description": "High-entropy string assigned to variable named 'secret'"
}
]
}Exit code 0 = no findings; 1 = any WARNING or above.
New in v3.0.0 GLORIA · deterministic output validated across
amd64/arm64in v4.1.0 Citadel
ForgeZero eliminates all known sources of non-determinism when --reproducible is active:
| Source of non-determinism | Elimination method |
|---|---|
| Random build IDs | -Wl,--build-id=none |
__DATE__ / __TIME__ macros |
SOURCE_DATE_EPOCH forced to last Git commit timestamp |
| Absolute paths in DWARF info | -fdebug-prefix-map=/absolute/path=. |
| Object sort order | Lexicographic sort before linker invocation |
fz -dir ./src --reproduciblereproducible: trueVerifying reproducibility:
# Machine A
fz -dir ./src --reproducible -out release_a
sha256sum release_a
# Machine B (same source, same commit)
fz -dir ./src --reproducible -out release_b
sha256sum release_b
# Both hashes must matchNew in v3.0.0 GLORIA
Generate a manifest:
fz verify --generate
fz verify --generate -manifest ./release.manifestThe manifest is a plain text file (one BLAKE3 hex hash + relative path per line).
Verify against a manifest:
fz verify
fz verify -manifest ./release.manifest
fz verify --strict # also report UNTRACKED filesThree categories of finding: MODIFIED, MISSING, UNTRACKED (only with --strict).
CI/CD integration:
fz verify -manifest ./release.manifest
# Exits 1 if any file has been tampered withSymlink boundary protection is always active and cannot be disabled. Every symlink target is resolved and checked against the project root. Symlinks escaping the project boundary are skipped with a warning.
New in v3.0.0 GLORIA
fz bench
fz bench -dir ./src
fz bench -n 5
fz bench -jsonOutput format:
fz bench — ForgeZero Build Profiler
Project: ./src Files: 12 Mode: auto Cache: cold
Phase Start (ns) Duration % Total
─────────────────────────────────────────────────────────────────────
Cache check 0 ns 421,330 ns 0.12%
Compile: src/main.c 421,330 ns 18,204,772 ns 5.21%
Compile: src/parser.c 31,035,105 ns 87,304,221 ns 24.99%
Pre-link symbol check 298,104,552 ns 1,203,449 ns 0.34%
Link 299,307,001 ns 46,882,004 ns 13.42%
─────────────────────────────────────────────────────────────────────
Total 349,323,226 ns 100.00%
JSON output:
{
"total_ns": 349323226,
"total_ms": 349,
"cache": "cold",
"phases": [
{ "name": "Compile: src/main.c", "start_ns": 421330, "duration_ns": 18204772 },
{ "name": "Link", "start_ns": 299307001, "duration_ns": 46882004 }
]
}New in v3.0.0 GLORIA
| Target triple | Runtime | Use case |
|---|---|---|
wasm32-emscripten |
Emscripten / Browser | Full libc emulation, JS glue loader |
wasm32-wasi |
Wasmtime, WasmEdge, WAMR | Server-side / cloud-native modules |
WASI via Zig (recommended — no extra SDK):
fz -cc main.c -zig -target wasm32-wasi -out main.wasm
wasmtime main.wasmEmscripten:
source /path/to/emsdk/emsdk_env.sh
fz -cc main.c -target wasm32-emscripten -out main.js
# Produces main.wasm + main.jsWASM config:
source_dirs:
- src
output: mymodule.wasm
backend: zig
target: wasm32-wasi
sanitize: false
flags:
cc:
- -O2
- -fvisibility=hiddenNote: ASan/UBSan are automatically disabled for
wasm32-*targets. Pass-sanitize=falseto suppress the notice.
Added in v1.6.0.
mkdir myproject && cd myproject
fz -init| File | Contents |
|---|---|
.fz.yaml |
Minimal project configuration with commented fields |
.fzignore |
Sensible default ignore rules |
README.md |
Project README template with fz build instructions |
No existing file is overwritten.
Added in v1.9.0.
fz -compile-commands
fz -dir ./src -compile-commands| Editor | Language server | Notes |
|---|---|---|
| Neovim | clangd | Install via Mason; auto-detects compile_commands.json |
| VSCode | clangd extension | Point it to the project root |
| CLion | Built-in | Opens compile_commands.json automatically |
| Helix | clangd | Set language-server = "clangd" in languages.toml |
| Emacs | eglot / lsp-mode | Both read compile_commands.json from project root |
Added in v1.9.0.
fz -updateWhat happens: fetches the latest release binary, backs up the current binary to fz.old, installs the new one, reports version change.
Rolling back:
sudo cp /usr/local/bin/fz.old /usr/local/bin/fzfz -asm hello.asm
fz -asm hello.s
fz -asm hello.fasm
fz -cc main.c
fz -cc main.cppmkdir myproject && cd myproject
fz -init
mkdir src
echo 'int main(void) { return 0; }' > src/main.c
fzfz -asm hello.asm -debug -verbose
gdb ./hellofz -asm boot.asm -mode raw -format bin -out boot.binfz -asm boot.asm -ld -out boot.elffz -cc main.c -strictfz -dir ./srcfz -dir ./src -j 0fz -cc main.c -target arm-linux-gnueabihf -sanitize=falsefz -cc main.c -zig -target aarch64-linux-musl -sanitize=falsefz -dir ./src -type static -lib mylib
ls libmylib.afz -cc mylib.c -shared -cc-flag "-O2 -fPIC" -o libmylib.sofz pm add github.com/me/my-lib
fz pm add github.com/me/my-lib@v1.2.3
fz pm install esp-idf
fz pm search crypto
fz pm list
fz pm update
fz pm remove my-libfz -dir ./src -compile-commands
cat compile_commands.json# .fz.yaml: source_dirs: [src, lib], output: release
fzfz sbom
cat sbom.cdx.jsonfz audit
fz audit -json | tee audit_report.jsonfz -dir ./src --reproducible
sha256sum ./srcfz verify --generate
fz verifyfz bench -dir ./src
fz bench -dir ./src -n 5 -json | tee bench_report.jsongit clone https://github.com/forgezero-cli/ForgeZero
cd ForgeZero
go build -o fz ./cmd/fz
# Run benchmark script (edit NUM_MODULES in script for different sizes)
./bench.sh
# Export results to Markdown
hyperfine --warmup 3 --prepare 'make clean && rm -rf .fz_objs fz_out' \
'./fz -dir . -out fz_out' 'make -j4' \
--export-markdown results.mdfz -cc main.c -zig -target wasm32-wasi -out main.wasm
wasmtime main.wasmsource /path/to/emsdk/emsdk_env.sh
fz -cc main.c -target wasm32-emscripten -out main.jsfz -dir ./src -json | tee build_report.jsonfz -dir ./kernel -watchfz -cc main.c -sanitize=false -out main_releasefz -dir ./src -keep-obj -verbose
ls .fz_objs/fz -shell
# fz> build main.c
# fz> set mode raw
# fz> build boot.asm
# fz> exitfz -dir . -cleansudo fz -update
# If something breaks:
sudo cp /usr/local/bin/fz.old /usr/local/bin/fz| Code | Meaning |
|---|---|
0 |
Success — binary produced; or fz verify / fz audit found no violations; or fz doctor is healthy. |
1 |
Build error — assembler, compiler, or linker failed; duplicate global symbol; fz verify found MODIFIED or MISSING files; fz audit found WARNING or above; fz doctor is degraded. |
2 |
Argument error — invalid or missing flags, source file not found, cross-compiler not on PATH, unreadable config file. |
export PATH="$PATH:$(go env GOPATH)/bin"sudo apt install nasm # Debian / Ubuntu
sudo dnf install nasm # Fedora
sudo pacman -S nasm # Arch
brew install nasm # macOS
pacman -S mingw-w64-x86_64-nasm # Windows MSYS2wget https://flatassembler.net/fasm-1.73.32.tgz
tar -xzf fasm-1.73.32.tgz
sudo cp fasm/fasm /usr/local/bin/
chmod +x /usr/local/bin/fasmsudo apt install g++ # Debian / Ubuntu
sudo dnf install gcc-c++ # Fedora
sudo pacman -S gcc # Arch (g++ included)
brew install gcc # macOSwget https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz
tar -xf zig-linux-x86_64-0.13.0.tar.xz
sudo mv zig-linux-x86_64-0.13.0 /opt/zig
echo 'export PATH="$PATH:/opt/zig"' >> ~/.bashrc
source ~/.bashrc
# Or via package manager
sudo pacman -S zig
brew install zigsudo apt install gcc-arm-linux-gnueabihf
sudo dnf install gcc-arm-linux-gnu
sudo pacman -S arm-linux-gnueabihf-gcc
# Or switch to the Zig backend to avoid all of this:
fz -cc main.c -zig -target arm-linux-gnueabihfDefine _start explicitly:
global _start
_start:
; ...Or use the C runtime:
fz -asm program.asm -mode cLikely -mode raw used with code referencing libc symbols. Switch to:
fz -asm program.asm -mode cCheck for conflicting global declarations. Skip the check when using weak symbols intentionally:
fz -dir ./src -no-symbol-checkFix the reported memory/UB issue. To temporarily disable:
fz -cc main.c -sanitize=falseExpected behavior. Pass -sanitize=false to suppress the notice.
Re-generate the manifest from the current known-good state:
fz verify --generateAnnotate the line with a suppression comment:
const char *example = "not-a-real-key"; // fz-audit: ignorefz -asm big_program.asm -timeout 300fz -dir . -clean
fz -dir ./src
# Or one-off:
fz -dir ./src -no-cachesudo apt install git
sudo dnf install git
sudo pacman -S git
brew install gitThe downloaded package content does not match the BLAKE3 hash in the catalog manifest. Do not use the package. Report the issue at github.com/forgezero-cli/catalog.
Ensure the file is in the project root. Regenerate after adding new source files:
fz -compile-commandssudo fz -updateEdit files from within the WSL2 terminal. This is a WSL2 kernel limitation with inotify when files are edited from Windows applications.
Ensure MSYS2 mingw64\bin is in your Windows PATH:
C:\msys64\mingw64\bin
| Feature | Status |
|---|---|
exclude patterns in config file |
✅ Done (v1.5.0) |
include patterns in config file |
✅ Done (v1.5.0) |
Multiple source_dirs |
✅ Done (v1.5.0) |
Explicit source_files list |
✅ Done (v1.5.0) |
libs field for library linking |
✅ Done (v1.5.0) |
flags.cc for C compiler flags |
✅ Done (v1.5.0) |
.fzignore file support |
✅ Done (v1.5.0) |
| Multi-level config merging | ✅ Done (v1.5.0) |
fz -init project scaffolding |
✅ Done (v1.6.0) |
-format bin flat binary output |
✅ Done (v1.6.0) |
Parallel builds (-j N) |
✅ Done (v1.7.0) |
Interactive shell (fz -shell) |
✅ Done (v1.7.0) |
C++ support (.cpp, .cc, .cxx) |
✅ Done (v1.7.0) |
Static library mode (-type static) |
✅ Done (v1.8.0) |
| Unique object file names (path-based) | ✅ Done (v1.8.0) |
Cross-compilation (-target <triple>) |
✅ Done (v1.9.0) |
LSP integration (-compile-commands) |
✅ Done (v1.9.0) |
| Smart self-update with rollback | ✅ Done (v1.9.0) |
| BLAKE3 hashing (7× faster cache) | ✅ Done (v2.0.0) |
Package manager (fz pm) |
✅ Done (v2.0.0) |
| Official package catalog | ✅ Done (v2.0.0) |
Shared library support (-shared) |
✅ Done (v2.0.0) |
Zig toolchain backend (-zig) |
✅ Done (v3.0.0) |
| SBOM generation (CycloneDX + BLAKE3) | ✅ Done (v3.0.0) |
SAST audit scanner (fz audit) |
✅ Done (v3.0.0) |
Reproducible builds (--reproducible) |
✅ Done (v3.0.0) |
Source tree verification (fz verify) |
✅ Done (v3.0.0) |
| Symlink boundary protection | ✅ Done (v3.0.0) |
Build profiler (fz bench) |
✅ Done (v3.0.0) |
| Race-condition-free parallel pipeline | ✅ Done (v3.0.0) |
| FASM native ELF64 auto-injection | ✅ Done (v3.0.0) |
WebAssembly (wasm32-emscripten / wasm32-wasi) |
✅ Done (v3.0.0) |
VFS abstraction + OpenVerified TOCTOU hardening |
✅ Done (v3.1.0) |
Aegis hardened RunCommand wrapper |
✅ Done (v3.1.0) |
SecureWriteFile atomic write pipeline |
✅ Done (v3.1.0) |
| Constant-time toolchain checksum verify | ✅ Done (v3.1.0) |
fz doctor self-audit command |
✅ Done (v3.1.0) |
Native Windows fs.Windows + rename retry |
✅ Done (v3.1.0) |
90%+ coverage + fs.Mock fault injection |
✅ Done (v3.1.0) |
Zero-allocation linker hot-path (0 allocs/op) |
✅ Done (v4.1.0) |
Stack-buffered syscalls (openHot, unlinkHot) |
✅ Done (v4.1.0) |
HADES ELF emitter refactor (correct .symtab ordering, fixed relocations) |
✅ Done (v4.1.0) |
Direct linker invocation (-ld flag) |
✅ Done (v4.1.0) |
Arena size override (--arena-size=N) |
✅ Done (v4.1.0) |
| Platform-specific syscall drivers (Linux/Windows/Darwin build tags) | ✅ Done (v4.1.0) |
Cross-architecture validation (amd64 + arm64) |
✅ Done (v4.1.0) |
| 400-module benchmark data point (4.95× speedup) | ✅ Done (v4.1.0) |
100% golangci-lint compliance (strict mode) |
✅ Done (v4.1.0) |
| Atomic SBOM generation helpers | ✅ Done (v4.1.0) |
| Colored terminal output (green success / red error) | Planned |
| GDB integration and improved debug workflow | Planned |
Man page (man fz) |
Planned |
| Windows native support without WSL2 | In progress |
| macOS full support and tested runtime | In progress |
Package:
internal/fs· Consumers:internal/utils,internal/doctor,internal/verify,internal/sbom,internal/pkgman
ForgeZero v3.1.0 introduces a deliberate separation between what filesystem operations the build tool requires and how the host operating system performs them. Goals: enable deterministic fault-injection tests without patching os globally, and centralize symlink and path-substitution defenses in one audited code path.
type FileSystem interface {
MkdirAll(path string, perm os.FileMode) error
WriteFile(path string, data []byte, perm os.FileMode) error
ReadFile(path string) ([]byte, error)
Open(path string) (io.ReadCloser, error)
OpenVerified(path string) (io.ReadCloser, error)
CreateTemp(dir, pattern string) (*os.File, error)
Remove(name string) error
RemoveAll(path string) error
Rename(oldpath, newpath string) error
Stat(name string) (os.FileInfo, error)
Lstat(name string) (os.FileInfo, error)
ReadDir(name string) ([]os.DirEntry, error)
Chmod(name string, mode os.FileMode) error
Readlink(name string) (string, error)
EvalSymlinks(path string) (string, error)
SameFile(a, b os.FileInfo) bool
}Binding model: fs.Default is set to Unix{} or Windows{} at compile time via build tags. utils.fileSystem() returns the active implementation.
OpenVerified on Unix:
pre, err := os.Lstat(path) // metadata without following final symlink
if pre.Mode()&os.ModeSymlink != 0 {
return nil, ErrSymlink // reject symlinks outright
}
f, err := os.Open(path)
post, err := f.Stat() // metadata of the opened fd
if !os.SameFile(pre, post) {
f.Close()
return nil, ErrPathChanged // inode/device changed between check and use
}
return f, nilWhy Lstat? Stat follows symlinks. An attacker could place a symlink at path; Lstat inspects the link node itself and rejects it immediately.
Why SameFile after open? This closes the TOCTOU window:
| Phase | Attacker action | Without SameFile |
With SameFile |
|---|---|---|---|
| T0 | path is a regular file |
Check passes | Lstat records inode A |
| T1 | Attacker replaces path with symlink to /etc/passwd |
— | — |
| T2 | Reader calls Open |
May read sensitive file | Open follows new symlink |
| T3 | Compare identities | — | os.SameFile(pre, post) fails → ErrPathChanged |
Native Windows support is a separate compilation unit, not a runtime fork:
| File | Build constraint | Purpose |
|---|---|---|
unix.go, default_unix.go, rename_unix.go |
!windows |
POSIX backend |
windows.go, default_windows.go, rename_windows.go |
windows |
Win32 API backend |
pathnorm.go |
all platforms | CleanPath, HasDrivePrefix, IsUNC, NormalizeAbs |
OpenVerified on Windows follows the same logical steps as Unix. Every entry point normalizes paths through CleanPath (drive letters, backslashes, UNC prefixes).
m := fs.NewMock(fs.Default)
m.SetFailOp("Rename", fs.ErrDiskFull)
m.SetFail("OpenVerified", resolvedPath, fs.ErrPermission)
utils.SetFileSystem(m)
defer utils.SetFileSystem(nil)| Variable | Simulated condition |
|---|---|
ErrDiskFull |
ENOSPC / quota exhaustion |
ErrPermission |
EACCES / EPERM |
ErrTimeout |
I/O timeout |
ErrInterrupted |
EINTR |
ErrSymlink |
Symlink policy rejection |
ErrPathChanged |
TOCTOU detection |
| Function | VFS operations |
|---|---|
SecureWriteFile |
MkdirAll, CreateTemp, Chmod, write, close, Rename, Chmod |
ReadFileSecure |
ResolveSecurePath → OpenVerified → read |
CopyFile |
OpenVerified source, temp file, Rename |
StatResolved / ReadDirResolved |
Stat, ReadDir on resolved paths |
Packages:
internal/utils,internal/fs,internal/pkgman,internal/assembler,internal/linker,internal/zig
Every external process spawned by ForgeZero — git, ar, zig, fasm, gcc, g++, clang, ld, nasm, objdump, nm, readelf — must pass through utils.RunCommand.
Stage 1 — Argument validation: ValidateCLIArg rejects shell metacharacters, backticks, pipes, redirection symbols, and embedded NUL/newline bytes.
Stage 2 — Absolute executable resolution: exec.LookPath resolves the binary; no bare-name invocations.
Stage 3 — Argument sanitization: Each argument is validated, preventing injection via malicious config flags or crafted filenames.
Stage 4 — Fixed environment:
| Variable | Value | Rationale |
|---|---|---|
LC_ALL |
C |
Stable locale sorting and diagnostics |
LANG |
C |
Same |
TZ |
UTC |
Reproducible timestamps |
SOURCE_DATE_EPOCH |
1600000000 |
Aligns with reproducible build expectations |
Stage 5 — Execution root: cmd.Dir is set to the project execution root so relative paths in tool invocations resolve inside the project tree.
atomicWrite algorithm:
CreateTempin the destination directory (same volume for atomic rename).Chmod(tmpName, 0600).- Write full payload.
Close(buffers flushed to kernel).renameResolved(tmpName, resolved).Chmod(resolved, 0600).
Files written through this path include: .fz.yaml updates from fz pm, .fz.manifest, compile_commands.json, SBOM outputs, and fz -init templates.
.fz.yaml may specify expected BLAKE3 digests per tool binary:
tool_checksums:
gcc: "abc123..."
nasm: "def456..."Comparison uses crypto/subtle.ConstantTimeCompare to remove timing side-channels. Mismatch produces tool checksum mismatch for <name> and fails the build before any compilation.
EnsureInsideRoot, HashDirWithRoot, and doctor's scanTree use ResolveSecurePath to ensure scanned paths do not escape the root via symlinks or .. segments after evaluation. This protection is an invariant, not a configurable option.
Package:
internal/doctor· Entry point:fz doctor [options]
fz doctor is a pre-flight diagnostic. It does not compile code. It answers whether the current machine satisfies ForgeZero's minimum operational requirements.
fz doctor
fz doctor -root /path/to/project
fz doctor -json
fz doctor -root ./myapp -jsonStage A — Toolchain Reachability: probes zig (required), fasm (required on non-Windows), wasm-ld (optional).
Stage B — Recursive Permission Audit: writes .fz_doctor_probe via SecureWriteFile, then walks the entire tree with OpenVerifiedRead on every regular file. Counts DirsScanned and FilesSeen.
Stage C — Platform Integrity:
| Field | Source |
|---|---|
platform.goos |
runtime.GOOS |
platform.goarch |
runtime.GOARCH |
platform.filesystem_impl |
fs.ImplName() (unix / windows) |
platform.execution_root |
utils.GetExecutionRoot() |
platform.num_cpu |
runtime.NumCPU() |
Stage D — Health Aggregation: Healthy = false if any required tool is missing, or if Readable or Writable is false.
fz doctor: ok
platform: linux/amd64 fs=unix sep="/" root=/home/dev/myproject cpus=16
toolchain:
zig (required): /usr/local/bin/zig
fasm (required): /usr/bin/fasm
wasm-ld: missing
permissions: root=/home/dev/myproject readable=true writable=true dirs=42 files=318
{
"status": "ok",
"healthy": true,
"toolchain": [
{ "name": "zig", "required": true, "found": true, "path": "/usr/local/bin/zig" },
{ "name": "fasm", "required": true, "found": true, "path": "/usr/bin/fasm" },
{ "name": "wasm-ld", "required": false, "found": false, "error": "not found in PATH" }
],
"permissions": {
"root": "/home/dev/myproject",
"writable": true,
"readable": true,
"dirs_scanned": 42,
"files_seen": 318
},
"platform": {
"goos": "linux",
"goarch": "amd64",
"path_separator": "/",
"filesystem_impl": "unix",
"execution_root": "/home/dev/myproject",
"num_cpu": 16
},
"errors": []
}CI gate usage:
fz doctor -json | jq -e '.healthy'GOOS=linux → compiles: unix.go, default_unix.go, rename_unix.go
GOOS=windows → compiles: windows.go, default_windows.go, rename_windows.go
GOOS=darwin → compiles: unix.go (stubbed syscall layer; builds succeed, runtime untested)
No if runtime.GOOS == "windows" exists inside OpenVerified. The correct struct is selected by the Go toolchain at compile time.
| Platform | Implementation | Syscalls |
|---|---|---|
| Linux | golang.org/x/sys/unix |
Direct SYS_OPENAT, SYS_UNLINKAT |
| Windows | golang.org/x/sys/windows |
CreateFile, ReadFile, WriteFile |
| Darwin | Stubbed syscall layer | Builds succeed; runtime untested |
openHot and unlinkHot use fixed-size stack buffers for UTF-8 path conversion on all platforms, achieving zero heap allocation in the hot path.
Windows may return sharing violations when AV or indexing software holds a handle. rename_windows.go implements bounded retry:
for attempt := 0; attempt < 8; attempt++ {
if err := os.Rename(oldpath, newpath); err == nil {
return nil
}
last = err
time.Sleep(time.Millisecond * time.Duration(10*(attempt+1)))
}
return lastBackoff: 10 ms, 20 ms, 30 ms, … up to 80 ms between attempts.
Verified builds for amd64 and arm64 across Linux, Windows, and Darwin. No CGO dependencies; pure Go + static C linkage where required. Binary digests are stable across rebuilds with identical inputs on all validated platforms.
Policy version: v4.1.0 · Command:
go test ./internal/... -cover
| Package | Coverage class | Notes |
|---|---|---|
internal/pkgman |
≥ 90% | HTTP catalog mock, runGit injection |
internal/fs |
≥ 90% | Mock all ops, OpenVerified, pathnorm |
internal/doctor |
≥ 90% | Permission failures, JSON, scanTree open errors |
internal/config |
≥ 90% | LoadMerged, Merge, validation |
internal/zig |
≥ 90% | RunCommand mock, link/compile failures |
internal/man |
100% | Man page generator |
internal/assembler |
high 80s–90s | Mocked runCommand, all target triple branches |
internal/linker |
high 70s–80s | Zero-allocation benchmarks, Windows impl, symbol parsers |
Allocation-tracking benchmarks for Linux and Windows targets enforce zero-allocation guarantees in critical paths:
BenchmarkCopyFileHot 0 allocs/op 0 B/op
BenchmarkResolveSymbols 0 allocs/op 0 B/op
BenchmarkEmitRelocations 0 allocs/op 0 B/op
Failure on any allocs/op > 0 in hot-path benchmarks blocks merge.
m := fs.NewMock(fs.Default)
m.SetFailOp("CreateTemp", fs.ErrDiskFull)
utils.SetFileSystem(m)
t.Cleanup(func() { utils.SetFileSystem(nil) })
err := utils.SecureWriteFile("out/config.yaml", data)
// expect error, no partial final filego test ./... -race
go test ./internal/... -coverprofile=coverage.out
go tool cover -func=coverage.out100% compliance with golangci-lint under strict configuration:
golangci-lint run -E gofmt,govet,staticcheck,unused ./...Zero lint warnings in main branch. All PRs must pass this check.
Pull requests touching internal/fs, internal/utils (I/O or RunCommand), or internal/doctor must:
- Include table-driven tests for new branches.
- Use
fs.Mockor existing seams for failure paths. - Not regress package coverage below the 90% bar.
- Pass
go test ./...locally before review. - Pass
golangci-lintin strict mode. - Include benchmark assertions for any new hot-path code —
0 allocs/opis the bar.
New in v4.1.0 Citadel
The HADES engine is ForgeZero's integrated codegen and ELF emission layer. It is responsible for transforming parsed AST nodes into correct, deterministic ELF64 object files without relying on external assembler binaries for supported instruction sets.
Symbol table ordering. The ELF specification requires local symbols to precede global symbols in .symtab. Earlier versions of ForgeZero emitted symbols in parse order, which could produce object files that technically linked but were non-compliant with strict linker expectations. v4.1.0 enforces local-before-global ordering unconditionally.
Absolute relocation offsets. call and jmp target resolution was recalculated to correctly compute offsets for absolute relocations. This was validated via a factorial execution test: a self-referential recursive function compiled and linked through the HADES pipeline produces exit code 120 (factorial(5)), confirming that call targets resolve to the correct runtime address.
Instruction/label disambiguation. The lexer/parser boundary now maintains strict differentiation between CPU instruction mnemonics and user-defined labels. In earlier versions, certain two-character mnemonics could be misidentified as label prefixes when appearing at column zero in specific syntactic contexts. v4.1.0 eliminates this ambiguity with explicit boundary checks, preventing silent object file corruption.
The parser maintains invariants at each AST node boundary. Malformed input that previously produced incomplete or silently incorrect AST nodes now triggers an explicit ErrMalformedAST error before codegen begins. Silent fallback degradation — where a corrupted AST would produce a broken but structurally valid ELF — is no longer possible.
The HADES codegen path (resolveSymbols, emitRelocations, copyFileHot) was refactored to eliminate all heap allocations from the hot path. All intermediate buffers are stack-allocated with fixed capacity. The codegen pipeline operates at hardware memory bandwidth limits (~1.18 GB/s on Intel i5-10310U) with zero GC interference.
"If you find an allocation in our hot paths — it's a bug."
The new -ld flag exposes the HADES linker invocation directly, bypassing the gcc/clang wrapper layer used in -mode auto and -mode c. This is distinct from -mode raw (which selects ld over gcc at the linking stage); -ld removes the compiler driver from the picture entirely at the build orchestration level. Overhead reduction is ~3–5% on small projects; on large builds dominated by codegen, the difference is negligible.
Use -ld when you need precise control over the linker command vector and do not want ForgeZero's compiler validation checks in the critical path.
Contributions are welcome: bug reports, feature requests, documentation improvements, and code patches.
-
Open an issue before starting significant work to align on the approach.
-
Fork the repository and create a descriptive feature branch (
feature/watch-debounce,fix/nasm-elf32). -
Write tests for new behavior:
go test ./... go test ./internal/... -cover golangci-lint run -E gofmt,govet,staticcheck,unused ./...
Security-sensitive changes must include fault-injection or mock tests per Section 35. Hot-path changes must include benchmark assertions (
0 allocs/op). -
Submit a Pull Request with a clear description of the change and the problem it solves.
Commit messages should be concise and use the imperative mood: "Add JSON output mode" not "Added JSON output mode".
Repository: github.com/forgezero-cli/ForgeZero
ForgeZero is released under the MIT License.
MIT License
Copyright (c) AlexVoste
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
If ForgeZero saves you time, consider giving the repository a ⭐️ on GitHub — it helps the project grow.
