An issue tracker that lives in your repository. Tickets are markdown files with a schema the CLI enforces — no account, no server, no sign-up.
$ moth list
backlog
280f4d Ship the binary high
8e0642 Parse the frontmatter none
brew install nikolasgioannou/tap/mothcurl -fsSL https://raw.githubusercontent.com/nikolasgioannou/moth/main/install.sh | shnpm install -g moth-cliThe npm launcher starts Node before it starts moth, which adds roughly 30ms per invocation. Homebrew and the install script give you the binary itself, so prefer them if you are running moth in a loop.
Or grab a binary from releases. macOS, Linux and Windows, glibc and musl.
On Alpine, the binary needs libstdc++, which the base image does not carry: apk add libstdc++. Node's Alpine images already have it.
moth init # one question per status, Enter accepts each
moth new "Parse the frontmatter" # prints the id it assigned
moth new "Ship the binary" --body "Needs the parser first."
moth edit 280f4d --blocked-by 8e0642 --priority high --label releaseThen ask what you can actually start, rather than what merely exists:
$ moth list --unblocked
backlog
8e0642 Parse the frontmatter none
280f4d is missing because it is waiting on 8e0642.
Name a ticket however you remember it. moth show 280f4d, moth show 280 and moth show "ship the binary" all find the same one, and an ambiguous reference lists the candidates rather than guessing.
moth schema --json reports exactly what this repository considers a legal ticket, which is how an agent learns the rules in one call.
Eleven, and that is the whole surface:
init— set up a repositorynew— file a ticketlist— the default view, priority then ageshow— one ticket in fullmove— change status, which is how you claim workedit— change any field, the body includeddelete— remove a ticketboard— a markdown board, grouped by statuscheck— validate every ticket against the schemaschema— this repository's rules, as JSONupgrade— update moth to the latest release
Each one's --help carries its flags and a worked example, and --json is available on everything that returns a ticket.
A folder of markdown files is free, but nothing enforces it. Session one's agent writes status: todo. Session three's agent, having never seen that file, writes state: in_progress. By session ten the folder is unqueryable, and "what's blocked?" means re-deriving a grep every time.
moth refuses writes a bare filesystem would accept: an unrecognised status, a field nobody declared, a parent that would form a cycle. An agent cannot invent status: blocked, because the write fails and says what is legal. Because the shape is known, querying it is a command rather than a grep somebody has to get right.
The refusals are the design, not gaps:
- No assignees, no accounts. Moving a ticket into a started status is how you claim it.
- No statuses outside six fixed categories. Name your own; each belongs to
backlog,unstarted,started,completed,canceledorduplicate. Queries by category work in any repository. - No undeclared fields. Custom fields are allowed, but must be declared in config first, so an agent can never introduce one.
- No comments, no activity log.
git log -pon a ticket is already a complete, attributed history. - No background update checks.
moth upgradecontacts the network when you run it, and never otherwise. A tool that starts in 12ms should not spend longer than that asking about itself. - No cycles, sprints, estimates, projects or manual ordering. No web UI, no TUI.
Every rejection, with its reasoning, is in the spec.
moth.config.yml at the root, and one markdown file per ticket in .moth/, named for its title so a directory listing reads like a backlog — fix-the-login-redirect-280f4d.md:
---
id: "280f4d"
title: Ship the first binary
status: backlog
priority: high
labels:
- release
created_at: 2026-08-31T02:31:59.759Z
updated_at: 2026-08-31T02:31:59.787Z
blocked_by:
- "8e0642"
---
Blocked on the parser landing.Flat, with nothing central that every write touches, so two branches creating tickets merge cleanly. Commit them with your code and they travel through branches, clones and pull requests.
- The v1 spec — what was built, and every rejected alternative
- Architecture decisions — four decisions, each with the alternatives that lost
- Contributing
moth tracks its own development in moth: the backlog is .moth/.