A blazing-fast code analytics tool that converts source code into comprehensive Markdown reports, optimized for modern developer workflows and LLM-powered tooling.
ZigZag recursively scans directories provided through CLI flags or a zig.conf.json configuration file and produces Markdown, HTML, and JSON reports containing your full source code, designed for modern workflows and tooling. Each Markdown report includes syntax-aware code blocks. The --llm-report flag produces a condensed, token-efficient report for LLM ingestion, with optional chunking for large codebases. The most recommended workflow is running zigzag init to initialize a zig.conf.json file with predefined defaults. ZigZag automatically ignores binary files to ensure outputs remain text-based and human-readable.
- Optimized file reading designed for high-performance processing
- Intelligent binary file detection prevents corrupted output and preserves human-readable format
- Flexible ignore patterns supports wildcards, extensions, and exact matches
- Persistent caching system with validation and atomic updates
- Parallel processing distributes tasks across worker pools for concurrent execution
- Cross-platform compatibility:
Windows,Linux, andmacOS - Timezone-aware timestamps with configurable offsets
- Multi-path support for processing multiple directories simultaneously
- Automatic directory skipping —
node_modules,.git,.turbo,.nx,.parcel-cache, and more - JSON configuration (
zig.conf.json) for project-level defaults - Real-time watch mode regenerates reports on file changes across MD, JSON, and HTML outputs
- HTML Dashboard (
--html) — interactive single-file dashboard with charts, virtual-scroll source viewer, and syntax highlighting; live-reloads in watch mode - LLM report (
--llm-report) — condensed, token-efficient report with per-file condensation; supports chunking via--chunk-sizefor large codebases - Phase progress — scan / aggregate / write phase indicators on stderr with a final rich summary (machine info, timings, file counts)
- Bench subcommand — per-phase timing table with CPU model and core count
- Upload to ZagForge (
--upload) — pushes the scan result to ZagForge; runs once on initial scan in watch mode
brew tap LegationPro/zigzag
brew install zigzagDownload the latest archive for your platform from the Releases page, extract it, and place the zigzag binary somewhere on your PATH.
macOS — Gatekeeper notice
Because the binary is downloaded from the internet, macOS will quarantine it on first run. After extracting, remove the flag with:
xattr -d com.apple.quarantine zigzagOr right-click the binary in Finder → Open → Open to approve it once.
The release binaries are ad-hoc code-signed, which avoids the "damaged and can't be opened" error on Apple Silicon. Full notarization requires an Apple Developer account; the Homebrew tap is the easiest path for a warning-free install.
- Zig 0.15.2
git clone https://github.com/LegationPro/zigzag.git
cd zigzagWith make (Linux/macOS):
make init # init submodules (sparse checkout)
make build # build release binaryWithout make (cross-platform):
python scripts/setup.py # init + build
python scripts/setup.py all # init + build + testThe executable will be available at zig-out/bin/zigzag.
# Create zig.conf.json with default values in the current directory
zigzag initGenerated zig.conf.json:
{
"paths": [],
"ignores": [],
"skip_cache": false,
"small_threshold": 1048576,
"mmap_threshold": 16777216,
"timezone": null,
"output": "report.md",
"watch": false,
"log": false,
"json_output": false,
"html_output": false,
"output_dir": "zigzag-reports",
"llm_report": false,
"llm_max_lines": 150,
"llm_description": null,
"llm_chunk_size": null,
"upload": false
}# Run using paths and options from zig.conf.json
zigzag run
# Run from config file, overriding specific options via CLI flags
zigzag run --paths ./src --ignores "*.test.zig"
zigzag run --watch{
"paths": ["docs", "src"],
"ignores": [".secret", ".env"],
"skip_cache": false,
"small_threshold": 1048576,
"mmap_threshold": 16777216,
"timezone": null,
"output": "report.md",
"watch": true,
"log": false,
"json_output": true,
"html_output": true,
"output_dir": "zigzag-reports",
"llm_report": true,
"llm_max_lines": 150,
"llm_description": null,
"llm_chunk_size": "500k",
"upload": false
}| Command | Description |
|---|---|
init |
Creates zig.conf.json with default values in the current directory. Warns and skips if the file already exists and is non-empty; overwrites silently if the file exists but is empty. |
run |
Loads zig.conf.json as the base config, then applies any CLI flags on top. Useful for project-level defaults. |
bench |
Runs the full report pipeline and prints a per-phase timing table (scan, aggregate, write) with CPU model and core count to stderr. |
Without a subcommand, ZigZag applies CLI flags directly (no file config is loaded).
| Flag | Type | Default | Description |
|---|---|---|---|
--version |
bool |
false |
Show ZigZag version. |
--help |
bool |
false |
Show help information. |
--skip-cache |
bool |
false |
Skip cache operations and clear the cache. |
--small |
number |
1048576 |
Threshold for small files in bytes. |
--mmap |
number |
16777216 |
Threshold for memory-mapped files in bytes. |
--paths |
string |
— | Directory to scan. Comma-separated or repeated for multiple paths. |
--ignores |
string[] |
[] |
Pattern(s) to ignore. Comma-separated or repeated for multiple patterns. |
--timezone |
string |
null |
Timezone offset (e.g. "+1", "-5:30"). |
--watch |
bool |
false |
Enable watch mode to regenerate reports on file changes. |
--no-watch |
bool |
false |
Disable watch mode, overriding "watch": true in zig.conf.json. |
--output |
string |
"report.md" |
Output filename for the Markdown report. |
--output-dir |
string |
"zigzag-reports" |
Directory to store generated reports. |
--json |
bool |
false |
Generate a JSON report alongside Markdown. |
--html |
bool |
false |
Generate an interactive HTML dashboard alongside Markdown. |
--llm-report |
bool |
false |
Generate a condensed LLM-optimised report. |
--chunk-size |
string |
— | Split the LLM report into chunks of this size (e.g. 500k, 2m). Omit or set to null in config for a single file. |
--log |
bool |
false |
Enable logging. |
--open |
bool |
false |
Automatically open the HTML report in a browser. |
--upload |
bool |
false |
Upload the scan result to ZagForge. Requires ZAGFORGE_API_KEY env var or ~/.zagforge/credentials. Only effective with the run subcommand or in watch mode (uploads once on initial scan). |
Settings are applied from lowest to highest priority (later values win):
- Hard-coded defaults
zig.conf.json(when usingzigzag run)- CLI flags (always override file config)
When the first --paths CLI flag is encountered, all file-loaded paths are replaced. Same for --ignores. Scalar fields (skip_cache, watch, etc.) always take the last CLI value. --no-watch is a permanent override: it forces watch = false regardless of the config file.
All fields supported in zig.conf.json:
| Field | Type | Default | Description |
|---|---|---|---|
paths |
string[] |
[] |
Directories to scan |
ignores |
string[] |
[] |
Patterns to exclude from scanning |
skip_cache |
bool |
false |
Bypass the cache and rebuild from scratch |
small_threshold |
number |
1048576 |
File size limit (bytes) for full in-memory reads |
mmap_threshold |
number |
16777216 |
File size limit (bytes) for memory-mapped I/O |
timezone |
string|null |
null |
Timezone offset for report timestamps |
output |
string |
"report.md" |
Output filename for Markdown reports |
watch |
bool |
false |
Enable filesystem watch mode |
log |
bool |
false |
Enable logging output |
json_output |
bool |
false |
Generate JSON reports |
html_output |
bool |
false |
Generate HTML dashboards |
output_dir |
string |
"zigzag-reports" |
Output directory for all reports |
llm_report |
bool |
false |
Enable LLM-optimized reporting |
llm_max_lines |
number |
150 |
Max lines per file in LLM reports |
llm_description |
string|null |
null |
Project description for LLM reports |
llm_chunk_size |
number|string|null |
null |
Split LLM report into chunks of this size. null or 0 = single file. Accepts numeric bytes or string with k/m suffixes (e.g. 500000 or "500k") |
upload |
bool |
false |
Upload scan result to ZagForge. Requires ZAGFORGE_API_KEY env var or ~/.zagforge/credentials |
Pass --llm-report (or set "llm_report": true in zig.conf.json) to generate a condensed report alongside the Markdown file. The LLM report is written as report.llm.md in the same directory.
Each source file is condensed: files exceeding llm_max_lines are truncated to the first 60 and last 20 lines with an omission notice. Boilerplate files (package-lock.json, yarn.lock, *.lock, etc.) are excluded automatically.
For large codebases, pass --chunk-size to split the output across multiple files. Accepts byte counts with optional k/m suffixes (case-insensitive):
zigzag run --paths ./src --llm-report --chunk-size 500k
zigzag run --paths ./src --llm-report --chunk-size 2mThe same format works in zig.conf.json — use a quoted string:
"llm_chunk_size": "500k"Plain numbers are also accepted for backward compatibility ("llm_chunk_size": 500000). Set to null (or omit) to disable chunking.
When chunking is active:
- Chunk 1 is written to
report.llm.md - Additional chunks are written to
report.llm-2.md,report.llm-3.md, … - A
report.llm.manifest.jsonis created listing all chunk files - Each continuation chunk starts with a
# Project: … (continued — chunk N)header
Files are never split across chunk boundaries.
ZigZag supports multiple ignore pattern types:
| Pattern Type | Example | Description |
|---|---|---|
| Wildcard Extension | *.png, *.svg |
Ignores all files with the specified extension |
| Exact Filename | test.txt, config.json |
Ignores files with an exact name match |
| Wildcard Prefix | test* |
Ignores files starting with the prefix |
| Wildcard Suffix | *config |
Ignores files ending with the suffix |
| Directory Name | node_modules, .cache |
Ignores directories and all their contents |
ZigZag automatically skips common directories:
node_modules,.bin.git,.svn,.hg.cache,.zig-cache__pycache__,.pytest_cachetarget,build,dist.idea,.vscode.turbo,.nx,.parcel-cachezig.conf.json
Binary files are automatically detected and excluded:
-
Extension-based (fast path):
- Images:
.png,.jpg,.jpeg,.gif,.bmp,.ico,.webp - Archives:
.zip,.tar,.gz,.7z,.rar,.bz2,.zst,.lz4,.xz,.lzma,.zstd - Executables:
.exe,.dll,.so,.dylib - Media:
.mp3,.mp4,.avi,.mov,.mkv - Fonts:
.woff,.woff2,.ttf,.otf,.eot - Compiled:
.class,.jar,.pyc,.o,.a - Documents:
.pdf - Databases:
.db,.sqlite
- Images:
-
Content-based (fallback): checks for null bytes and non-printable character ratio in the first 512 bytes.
Each processed directory contains a report.md file with the following structure:
# Code Report for: `./src`
Generated on: 2026-01-30 14:23:45 (UTC+1)
---
## Table of Contents
- [./src/main.zig](#./src/main.zig)
- [./src/utils.zig](#./src/utils.zig)
...
---
## File: `./src/main.zig`
**Metadata:**
- **Size:** 2.28 KB
- **Language:** zig
- **Last Modified:** 2026-01-23 10:38:54 (UTC+1)
```zig
const std = @import("std");
// ... file content
```
...Watch mode uses OS-level filesystem events (inotify on Linux, kqueue on macOS/BSD, ReadDirectoryChangesW on Windows) to detect changes instantly. Only the changed file is re-read from disk; the report is rebuilt from the in-memory state of all other files.
Events are debounced: rapid changes within a 50 ms window are batched into a single report write.
In HTML mode, a lightweight .stamp sidecar file is written alongside the HTML report. The browser polls the stamp file instead of the full HTML, then fetches the HTML only when the stamp changes.
When --upload is also active, ZigZag uploads the initial snapshot once after the first scan and does not re-upload on subsequent changes.
Use --no-watch to override "watch": true set in zig.conf.json and run a single-shot report instead:
zigzag run --no-watchPress Ctrl+C to stop.
Pass --upload (or set "upload": true in zig.conf.json) to push the scan result to ZagForge after the report is written.
zigzag run --uploadThe API key is discovered in this order:
ZAGFORGE_API_KEYenvironment variable~/.zagforge/credentialsfile containing aZAGFORGE_API_KEY=zf_pk_…line
When --upload is active in watch mode, ZigZag uploads the initial snapshot once after the first scan completes. It does not re-upload on subsequent file changes — use zigzag run --upload for one-shot uploads.
Upload requests time out after 30 seconds. If the request fails, an error is printed to stderr and the watch loop continues.
Passing --upload without the run subcommand (i.e. in flag-only mode) has no effect. ZigZag will print a warning:
warning: --upload has no effect without the 'run' subcommand
warning: Usage: zigzag run --upload
Pass --json (or set "json_output": true) to generate a machine-readable JSON report alongside the Markdown file. The JSON file uses .json replacing .md (e.g. report.json).
{
"meta": {
"version": "0.16.0",
"generated_at_ns": 1738245534000000000,
"scanned_paths": ["./src"]
},
"summary": {
"source_files": 12,
"binary_files": 3,
"total_lines": 1450,
"total_size_bytes": 58320,
"languages": [
{ "name": "zig", "files": 10, "lines": 1300, "size_bytes": 52000 }
]
},
"files": [
{
"path": "./src/main.zig",
"size": 2345,
"mtime_ns": 1738245534000000000,
"extension": ".zig",
"language": "zig",
"lines": 87
}
],
"binaries": [
{
"path": "./src/assets/logo.png",
"size": 4096,
"mtime_ns": 1738240000000000000,
"extension": ".png"
}
]
}Pass --html (or set "html_output": true) to generate a self-contained interactive HTML report. The HTML file is written next to the Markdown with .html replacing .md.
The dashboard is a single .html file — all CSS, JavaScript, and syntax highlighting are bundled. Open it directly in any browser; no server required.
| Feature | Description |
|---|---|
| Summary cards | Total files, lines, size, and languages at a glance |
| Language chart | Bar chart of file counts per language |
| Size distribution | Histogram of file sizes across the codebase |
| File table | Sortable, searchable table with path, language, size, and line count |
| Source viewer | Click any file to open a slide-in panel showing its source code |
| Syntax highlighting | Off-thread Prism highlighting for 20+ languages |
| Virtual scroll | Files over 500 lines or 200 KB use virtual scrolling — only visible lines are rendered |
| Dark mode | Follows the OS prefers-color-scheme setting automatically |
| Watch live-reload | Polls a .stamp sidecar file; reloads the report without a full page refresh |
Zig, JavaScript, TypeScript, Lua, JSON, HTML/XML/SVG, CSS, SCSS, Bash/Shell, C, C++, Rust, Go, Python, Ruby, Java, Markdown, TOML, YAML, SQL.
ZigZag includes a smart caching system that:
- Persists between runs in
.cache/files/ - Validates on startup to remove stale entries
- Uses file metadata (mtime, size) for change detection
- Performs atomic updates to prevent corruption
Cache location: ./.cache/ (relative to working directory)
Files are read fully into memory using readFileAlloc. The --small and --mmap thresholds configure the readFileAuto utility (which supports mmap and chunked streaming for large files), but the main processing path loads each file directly into memory regardless of size.
- Use cache: Don't use
--skip-cacheunless necessary - Tune thresholds: Adjust
--smalland--mmapbased on your file sizes - Parallel processing: More threads for large projects (auto-detected from CPU cores)
- Ignore patterns: Use specific patterns to exclude unnecessary files early
# Run all tests (via Makefile — avoids WSL2 output-buffering hang)
make test
# Direct invocation
zig test --dep options -Mroot=src/root.zig -Moptions=src/cli/version/fallback.zigNote:
zig build testmay hang indefinitely on WSL2 due to output buffering. Usemake testinstead.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing) - Open a Pull Request
git clone https://github.com/LegationPro/zigzag.git
cd zigzag
# Init submodules + build (make)
make init && make build
# Init submodules + build (cross-platform, no make required)
python scripts/setup.py
# Run tests (make)
make test
# Run tests (cross-platform)
python scripts/setup.py test
# Format code
zig fmt src/- Follow Zig's standard formatting (
zig fmt) - Write tests for new features in
*_test.zigfiles alongside their modules - No inline tests in main modules under
report/andconfig/— use_test.zigfiles - Register new test files in
src/root.zig
MIT License — see LICENSE.md for details.
- ZagForge — official product page
- Documentation
- GitHub Repository
- Issue Tracker
- Zig Language
Made with ❤️ using Zig
