clu

Design notes

Why clu is built the way it is — local-first, single file, pure Go.

A few load-bearing choices shape how clu behaves. They're worth knowing because they explain what clu is good at — and what it deliberately leaves out.

Local-first, one file

Everything for a project lives in a single SQLite database at .clu/data.sqlite — no server, no daemon, no account, no network. To share a tracker, copy the file; to throw it away, delete it. Every other choice follows from this one. See the storage layout.

Pure Go, no CGo

clu uses the pure-Go SQLite driver (modernc.org/sqlite), so the binary is a single static executable: nothing to compile against, no system libraries to install. go install and you're done.

One identity

There's a single identity flag, -a / --agent <name>. It's both the lane filter (which work do I pull from?) and the actor recorded in the audit log (who did the thing?). clu doesn't separate "users" from "agents" — on a local tool there's no second actor to track. See multi-agent.

In-place, append-only migrations

The schema upgrades itself on first run, gated by SQLite's user_version. Migrations are append-only and forward-only, so opening an older database with a newer clu just works — it never rewrites or discards your data.

Atomic by construction

The one operation where races matter — claim — is a single SQL statement (UPDATE … RETURNING with a subquery), so two agents claiming at the same time always get different issues. Cancel-cascade and cycle checks use recursive SQL. See status semantics.

Contributing

clu is open source. Code conventions, the migration list, and the test setup live in the repository — start there if you want to hack on it.

On this page