Plan it. Prove it. Done in Git.
Project management that lives in your repo, built for developers and the AI agents working beside them. Tasks, docs, plans and API proofs are Markdown files in git. Every change is one commit. One local binary serves the terminal and the browser: no server to run, no account to create.
dit-full-tour-720p.mp4
This repository publishes DIT's releases: the macOS app, the command-line binaries and the installer. It holds no source code.
When AI agents and developers share a codebase, the plan usually lives in a chat thread. Two sessions pick the same task. A screen is built on an endpoint that doesn't exist yet. Every agent burns tokens grepping its way around the code.
Git already gives you what a tracker builds from scratch, so DIT uses git as the database:
| You need | Git already has it |
|---|---|
| Change history | git log, git blame |
| Who changed what | every commit has an author |
| Sync and offline work | every clone is the whole workspace |
| Review | pull requests |
| Permissions | who can push |
| Backup | every clone, again |
Issues, docs, flows and API scenarios are plain Markdown in your repository, readable in any editor and diffable in any pull request. A local index makes queries fast; it is rebuilt from git at any time and is never the truth.
One plan that every session, human or agent, reads before it starts and writes to as it works.
- Lanes and claims. Each work stream is a lane.
dit claimmarks a task as yours, and a claim expires when a session goes quiet, so nothing stays locked by an agent that stopped. - Readiness is computed, never typed.
dit ready --lane frontendlists what can start right now.blocked_bygates work;fed_bydraws a handoff and blocks nothing. - The whole picture in one command.
dit flow showprints every issue by stage and lane, who holds what, and the critical path. The browser draws it as a diagram. - Nobody has to be asked. A question on an issue lands in that lane's
dit inbox. - Every agent reads the same rules.
dit ai initwrites the agent guide, anddit ai specprints it from the binary you run, so the guide never drifts from the tool.
$ dit ready --lane frontend
#6 frontend Place order button and confirmation screen
held until proven (blockers are through; the seam is not):
#7 frontend Pay by card step
needs pay-intent: not proven on local"Done on the board, 404 on staging" stops here. Morse keeps API scenarios in your docs, pinned to the OpenAPI spec they were written against.
- Declarative chains, no scripts. Steps call operations from the spec,
check
expect, andcapturevalues for the next step. A scenario is a fence in any Markdown document, reviewed in pull requests like code. - Proof is recorded, and only on green.
dit morse sync <scenario> --env <name>fires the chain and, only when every step passes, records where and when it was proven. When the spec moves, the proof goes stale and says so. - Seams gate work. Mark an issue
needs_scenarios=place-order, and withproof: requiredit stays out ofdit readyuntil that scenario holds on the environment that lane uses. - Run it in the browser. Pick an environment, press Run, and see every step checked.
- Safe in a shared repo. Nothing fires on its own; requests go only to
hosts your machine allowed; environments and secrets stay in a gitignored
file. Bring existing work in with
dit morse import curlordit morse import postman. - Catch the 404 before it ships.
dit code apireads every path your frontend calls against the specs and lists the calls no spec describes.
A code map of TypeScript, Rust and Kotlin, derived from git at HEAD: who imports a file, what it uses, how two files connect, where a question is answered. One call, a fraction of the tokens.
$ dit code users useResourceList # who breaks if this changes
$ dit code path SerpaShell tokenStore
$ dit code where token refreshFour questions an agent asks before a change, answered with grep, graphify
and dit code on a 6,371-file React and TypeScript app:
| Question | grep | graphify | dit code |
|---|---|---|---|
Who imports useResourceList? |
1,889 tokens | 1,598 tokens, 28 of 30 | 286 tokens, 30 of 30 |
What does PayrollRunsPage.tsx import? |
358 tokens, unresolved | 481 tokens, 13 of 16 | 201 tokens, 16 of 16 |
How does SerpaShell reach tokenStore? |
can't answer | 37 tokens | 21 tokens |
| Where is token refresh handled? | 5,283 tokens | 1,609 tokens | 154 tokens |
- The cheapest correct answer on 4 of 4 questions, and 34× fewer tokens than grep on the question asked in words.
- Never stale. Every command brings the map up to HEAD first, in 0.15 s when nothing changed (graphify's update took 84 s).
- Zero cost until asked. No agent hook is installed, so it adds 0 tokens to calls that don't use it.
- Works in any git repository, with no setup.
Measured 2026-09-27 with dit 0.9.0. Tokens are bytes ÷ 4 for every tool; an answer that misses part of the result cannot win, however small.
Work
- A board with one column per status; drag a card and a commit lands.
- Home: what changed, what waits on you, and a one-line capture bar.
- Search in words or DQL (
status != done AND lane = frontend), saved as views. - Relations both ways, drawn as a graph: what an issue waits on, what waits on
it, its epic, and every
[[#6]]link. - Epics with progress derived from their children.
Plan
- Timeline: every change from git history, and the board as it stood on any date or release.
- Roadmap of epics and releases.
- Gantt: drag a bar and the dates change in the file, as a commit, with the critical path drawn.
Docs
- Pages from 11 templates: BRD, PRD, SRS, FSD, business flow, TSD, data model, API contract, ADR, test plan and release notes.
- Edited in place in the browser, saved as Markdown commits, linked to issues.
- Images attached in plain git.
Git-native
- A merge driver that merges two edits to one issue field by field. Both changes land; only a real clash asks a person.
dit syncfetches, rebases and pushes. Switch on background sync and every machine you work from stays current on its own.dit doctorchecks everything that silently breaks a workspace.- Works offline. Every clone is the whole workspace.
One binary
- The terminal and the browser work on the same workspace at once.
dit ui --allserves every workspace on your machine; on macOS a menu bar app starts it, on Linux you run it from a terminal.- The browser UI stays on
127.0.0.1and opens with a per-session token.
mkdir my-tracker && cd my-tracker # any git repo becomes the workspace
dit init # git init, merge driver, README
dit issue new "Fix the login flow" -P p1
dit list "status != done"
dit ui # the board, flows, docs and Morse in your browsermacOS app (menu bar), with Homebrew:
brew install --cask faridlab/tap/ditThis installs DIT.app and puts dit on your PATH.
Command line, macOS or Linux:
curl -fsSL https://github.com/faridlab/dit/releases/latest/download/install.sh | bashThe installer downloads the release binary for your platform and installs it only if it matches the SHA-256 published beside it. On Linux it picks the glibc build where glibc is 2.39 or newer, and the static build everywhere else.
By hand: download the archive for your platform from the
latest release, check it
against its .sha256 file, and put dit on your PATH.
Requirements
- Any distribution. The glibc builds need glibc 2.39 (Ubuntu 24.04, Debian 13, Fedora 40, or later); everywhere else — Ubuntu 22.04, Debian 12, Alpine — the installer picks the static build, which has no such need.
- git 2.28 or newer, and
bashandcurlfor the installer.
Where it goes. The installer puts dit in ~/.cargo/bin if that folder
exists, otherwise in ~/.local/bin, and tells you when that folder is not on
your PATH.
Running the browser UI. There is no menu bar app on Linux. Run the server from a terminal and leave it open:
dit ui --all # every workspace on this machine, at http://127.0.0.1:7700It opens your browser with xdg-open. Stop it with Ctrl-C. To keep a workspace
synced without the UI, run dit sync --watch instead (see below).
Notifications. In the browser, open Settings → Notifications → Notify me. While the DIT tab is in the background, you are told when another session or machine finishes an issue, makes one ready to pick, or comments.
A laptop and a desktop, or a Mac and a Linux PC, can share one workspace. Git carries everything: no extra server, no account. Push the workspace to any git remote you both can reach, then on each machine:
git clone <your-workspace-remote> && cd <workspace>
dit install-driver # register the merge driver; sync refuses to run without it
dit workspace add . --name <name>
dit workspace sync <name> on # sync in the background while dit ui runs
export DIT_ME=<you>-<machine> # one name per machine, e.g. farid-mac and farid-pc- Background sync pulls every 15 seconds and pushes a few seconds after
each commit, for as long as
dit ui(or the macOS menu bar app) runs. On a machine without the UI,dit sync --watchdoes the same in a terminal. It is switched on per machine, never by the repository. - AI sessions don't poll.
dit wait ready --lane <lane>ordit wait '#12'blocks until that holds, then prints what other sessions finished and what became ready. Run it in the background. - When something stops it, such as an uncommitted file or a merge
conflict,
dit doctorsays what and where.
A separate DIT_ME per machine matters: claims and "what changed while I
waited" both go by that name.
One DIT for a whole team, on a server: everyone opens a URL and signs in, and every change is saved in their name — product people, designers and clients never install anything or see git. Developers and their AI sessions keep working from their own clones; the server is one more clone that syncs with your team's GitHub or GitLab.
You need a Linux server, a domain name pointing at it, and Caddy for HTTPS. The steps below are for Debian or Ubuntu.
1. A user for DIT, and DIT for that user.
sudo apt-get install -y git curl
sudo useradd --system --create-home --home-dir /var/lib/dit --shell /usr/sbin/nologin dit
curl -fsSL https://github.com/faridlab/dit/releases/latest/download/install.sh -o /tmp/install-dit.sh
sudo -H -u dit bash /tmp/install-dit.sh # installs /var/lib/dit/.local/bin/dit2. The workspace, from your forge. Give the server its own key, add it to the repository as a deploy key with write access, then clone:
sudo -H -u dit mkdir -p /var/lib/dit/.ssh /var/lib/dit/workspaces
sudo -H -u dit ssh-keygen -t ed25519 -N "" -f /var/lib/dit/.ssh/id_ed25519
sudo cat /var/lib/dit/.ssh/id_ed25519.pub # paste as a deploy key
sudo -H -u dit git clone git@github.com:acme/plan.git /var/lib/dit/workspaces/plan
sudo -H -u dit bash -c 'cd ~/workspaces/plan && ~/.local/bin/dit install-driver &&
~/.local/bin/dit workspace add . --name plan && ~/.local/bin/dit workspace sync plan on'3. HTTPS with Caddy. In /etc/caddy/Caddyfile, then sudo systemctl reload caddy:
dit.example.com {
reverse_proxy 127.0.0.1:7700
}
4. The first admin. Run the server once by hand with --init; it prints a
link, valid for an hour, that makes you its admin. Open it, then stop the
server with Ctrl-C.
sudo -H -u dit /var/lib/dit/.local/bin/dit serve --public-url https://dit.example.com \
--root /var/lib/dit/workspaces --init5. Keep it running. /etc/systemd/system/dit.service, then
sudo systemctl enable --now dit:
[Unit]
Description=DIT team server
After=network-online.target
Wants=network-online.target
[Service]
User=dit
Group=dit
Environment=HOME=/var/lib/dit
WorkingDirectory=/var/lib/dit
ExecStart=/var/lib/dit/.local/bin/dit serve --public-url https://dit.example.com --root /var/lib/dit/workspaces
Restart=on-failure
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/dit
[Install]
WantedBy=multi-user.targetLeave --init out of the service: it only works while nobody has an account.
Adding more workspaces needs no terminal: in DIT, Settings → Workspaces
→ Show the deploy key (add it to the repository, with write access), then
Add workspace with its name and git@github.com:acme/another.git. The
list there says when a workspace's sync needs attention. Step 2 above is the
same thing from a terminal, for the first one.
6. Invite the team. In DIT, Settings → People → Make invite link, choose a role, and send the link. Viewers read everything, members work on issues and docs, admins also manage people. A link works once, for 7 days.
A team server is deliberately less capable than DIT on your own machine: it
never sends Morse requests (prove scenarios from your machine with
dit morse sync), keeps no one's environment secrets, and reads only its
workspaces' own repositories.
Upgrade:
sudo -H -u dit /var/lib/dit/.local/bin/dit upgrade && sudo systemctl restart ditdit upgrade # the command line installed by the installer or by hand
brew upgrade --cask dit#DIT · #weDitit — Plan it. Prove it. Done in Git.
Open an issue here. Include
dit --version and how you installed DIT.
