A code duplication scanner for Zig code.
  • Zig 94.1%
  • Python 4.5%
  • Shell 1.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Mikael Säker 26ae7ce1d7
All checks were successful
test / test (push) Successful in 33s
Build and run on Zig 0.17
- build.zig: Run.addPassthruArgs() replaces b.args; the corpus step takes
  the stdlib as a LazyPath (std.Build.LazyPath.zig_lib) now that
  b.graph.zig_lib_directory is gone.
- std.zig.Ast.parse takes ParseOptions: `.zig` becomes `.{}` (mode .zig,
  recovering, as before).
- forEachChild: `errdefer` lost its |err| capture in 0.17, so its data is
  a plain .node like `defer` (reading .opt_token_and_node panicked on any
  file with an errdefer); `array_mult` is gone with the `**` operator.
  `zig build corpus` walks the whole 0.17 stdlib without a panic.
- CI: the runner's PATH finds Zig 0.16; run on /opt/zig-0.17.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011uZv7g8MJCfBfN4FRzUVtt
2026-10-06 13:15:13 +02:00
.forgejo/workflows Build and run on Zig 0.17 2026-10-06 13:15:13 +02:00
scripts scripts: setup-tools, the one every repo shares 2026-09-22 21:06:15 +02:00
src Build and run on Zig 0.17 2026-10-06 13:15:13 +02:00
tools Retune sequence windows; delete redundant size bucketing 2026-07-25 11:39:36 +02:00
.gitignore scripts: setup-tools, the one every repo shares 2026-09-22 21:06:15 +02:00
build.zig Build and run on Zig 0.17 2026-10-06 13:15:13 +02:00
CLAUDE.md Add simgrep: structural clone detector for Zig codebases 2025-12-25 11:26:28 +01:00
context.md Bound recursion depth in fingerprinting 2026-07-25 20:07:06 +02:00
DESIGN.md Bound recursion depth in fingerprinting 2026-07-25 20:07:06 +02:00
LICENSE Add MIT license 2026-07-25 12:00:19 +02:00
README.md Add README 2026-07-25 12:01:57 +02:00

simgrep

A fast structural clone detector for Zig codebases. It finds duplicated shapes — code with the same AST structure but different identifiers and literals — which plain text search and line-diff tools miss.

How it works

File Walker ──▶ Zig Parser ──▶ Fingerprint ──▶ Report
                (std.zig)      (Merkle)

Each subtree is fingerprinted with a Merkle hash computed over its structure, not its spelling, so fn addUser(a: u32) and fn addItem(b: u32) with the same body collapse to the same fingerprint.

Build

zig build -Doptimize=ReleaseFast
# -> zig-out/bin/simgrep

Usage

simgrep [options] <path>
option meaning
--min-lines=N minimum line span to consider (default: 5)
--min-clones=M minimum occurrences to report (default: 2)
--format=FORMAT text or json (default: text)
--ignore=PATTERN skip matching paths, repeatable
--show-source, -s show the code of each clone's first occurrence
--no-fail always exit 0, even when clones are found
--help, -h show help

Ignore patterns: * does not cross /, ** does. A pattern without / also matches any path component — --ignore=*_test.zig skips test files anywhere.

Exit status

code meaning
0 no clones found
2 clones found (suppress with --no-fail)

The non-zero exit on findings is deliberate, so simgrep can gate a build.

Examples

# survey a source tree, ignoring tests
simgrep --ignore=*_test.zig src/

# only substantial, widespread duplication
simgrep --min-lines=20 --min-clones=3 src/

# machine-readable, never fail the build
simgrep --format=json --no-fail src/

Raising --min-lines is the main noise control: short spans repeat naturally in any codebase, so the default of 5 is a floor rather than a recommendation for large trees.

License

MIT — see LICENSE.