ForgeGraph
Incremental Build Engine & DAG Visualizer
- Go
- React
- TypeScript
- Vite
- React Flow
- SSE

Problem
Task graphs waste time redoing unchanged work, and cache misses that only say “hash changed” are not actionable. ForgeGraph is a local engine: schedule independent work concurrently, skip what is still valid, and explain every hit or miss.
System design
Key engineering decisions
Content, not mtime
Cache identity is SHA-256 over schema version, task name, canonical command, configured env-value digests, sorted input paths and file bytes, and sorted dependency result fingerprints. A hit also requires declared outputs to still exist.
Downstream fingerprints
Result fingerprints include output hashes so a mutated producer output invalidates dependents even if the producer itself cache-hits.
Bounded workers
Remaining-dependency counts plus a worker pool (`--jobs`, default from the Go runtime). Dependents start only after every dependency succeeded or cache-hit. Failed deps skip downstream; unrelated branches continue.
One event stream
Immutable events fan out to the CLI, JSON report, `.forge/runs` history, and SSE. Cancelled work is never written as a successful cache record.
Single binary
The Vite/React UI is embedded with `go:embed`. `forge ui` binds loopback at `127.0.0.1:7331`.
Measured results
Local demo (`examples/demo-project`). Warm is 1ms, so this is reported as cards rather than a bar chart that would hide it.
Cold
1.33s
11 executed · 0 hits · max parallel 3
Warm
1ms
0 executed · 11/11 hits
One-file change
1.09s
4 executed · 7 hits
Cold sum of task durations was 2.62s against 1.33s wall clock (max observed parallelism 3). After changing src/api.txt, invalidated tasks were compile-api, unit-api, integration, and package. Unrelated branches remained cache hits.
Explainability

Cache miss Changed input: src/api.txt Previous SHA-256: a3d82997… Current SHA-256: 443ebffd…
Verification
- 56 Go tests
- 4 frontend tests
go test -race ./...passedgo vet ./...passed- TypeScript typecheck and Vite production build passed
- CI passed on the v0.1.0 tag
Release commit 9adfbeae5403ff5abdd916d80452229ea03912aa
Engineering notes
Validation caught a scheduler race (workers started before the initial enqueue), a re-lock deadlock risk on task finish, aggregate targets counted as executed, dashboard polling that overwrote a selected historical run, and run IDs containing dots being rejected. Those are fixed in v0.1.0.
What I learned
Cache correctness is an invariant problem, not a hash-printout problem. Fingerprints have to be ordered, env values digested rather than stored, and cancellation must not write success. Observability is cheaper when CLI, history, and UI share one event model.