Install from source
There is no brew install yet — the release pipeline that would cut and pin a tap formula hasn't shipped (see thestatus note on the home page). Go only; nothing else to install.
git clone https://github.com/kazi-org/dira
cd dira
go build ./cmd/dira
./dira --helpThe toolchain version is the one pinned in go.mod. The command path is stdlib-only, enforced by a test — dira runs inside a hook while a human waits on a prompt.
Every command
One line and one real dira help <verb>block per command below, generated from this build — not a hand-typed list, so it cannot drift from what the binary actually does.
dira help
Show usage for dira itself or for one command.
dira help - show usage for dira or one of its commandsdira init
Seed a new personal or workspace ledger by answering a short interview.
dira init - seed a new personal or workspace ledger.
usage:
dira init --interview [flags]
Asks a short, fixed set of questions on stdout and reads one answer per
question from stdin: which tier you are seeding, one intent, one
constraint, and one open question. dira asks; it never generates a
question or interprets an answer (dec-0003).
Either every answer is written or none are: an aborted or unanswered
interview leaves no .dira behind at all (dec-0010).
flags:
-C <dir> run as if started in this directory
-interview seed a ledger by answering the interview
-tier <tier> person or workspace — must match the interview's own answer
exit codes:
0 a new ledger was seeded
1 the interview was incomplete, or the ledger could not be written
2 usage errordira log
Write a new entry to the ledger, or add an edge to one that already exists.
dira log - write an entry to the ledger, or add an edge to one
usage:
dira log --kind KIND --title TEXT [flags] write a new entry
dira log --stdin write an entry read from stdin
dira log ID --edge TYPE=TARGET add an edge to an existing entry
The id is allocated by dira: the lowest unused number for the kind.
Each invocation writes exactly one file under .dira/entries/.
entry flags:
--kind KIND intent, decision, question, constraint or note
--title TEXT one line, legible with no context
--state STATE defaults to the first state valid for the kind
--body TEXT the prose because; - reads it from stdin
--tag TAG repeatable
--edge TYPE=TARGET repeatable, e.g. derives_from=int-0002
--edge-note TEXT one line on why the preceding --edge exists
--alternative TEXT repeatable; the road not taken
--why-not TEXT why the preceding --alternative was not taken
--revisit-if TEXT what would reopen the preceding --alternative
--hook HOOK capture point; defaults to manual
--session ID opaque session id this entry came from
--excerpt TEXT the transcript fragment it was inferred from
--tier TIER regex, semantic or human
--confirmed-by WHO who dispositioned it, e.g. human
--adr PATH path to a mirrored ADR file
--private never exportable to a downstream ledger
--stdin read the whole entry from stdin; excludes the flags above
-C DIR run as if started in this directory
A decision must record at least one alternative with its --why-not.
`dira log ID` takes --edge, --edge-note and --tag: it adds to an entry,
and never rewrites one.dira sniff
Read the current session transcript for decisions and stage them for review.
dira sniff - read the session for decisions and stage them for review
usage:
dira sniff show what the last turn would stage
dira sniff --stage --quiet stage it; the Stop hook's invocation
dira sniff --deep --stage --all
stage the whole transcript and print the
tier-2 handoff; the PreCompact invocation
dira sniff --transcript FILE read a transcript from disk
Everything it writes is `state: staged` with `source.tier: regex`, and
waits for you. A regular expression has no business asserting rationale,
so nothing here is ever accepted, carries an alternative, or claims a
reason (dec-0003). Confirming one is a separate verb.
flags:
--stage write the candidates; without it nothing is written
--quiet print nothing unless something was staged
--all read the whole transcript, not just the last turn
--deep also print the tier-2 handoff block; requires --stage
--hook HOOK capture point recorded: Stop, PreCompact or manual
--session ID session id; taken from the hook payload when absent
--transcript FILE read this file instead of stdin
-C DIR run as if started in this directory
--deep stages exactly what --stage stages, through the same writer, and
then prints a handoff block naming the ids it wrote and the fields only
a reader of the conversation can supply. It upgrades nothing: every
entry is still staged and still `source.tier: regex`. It defaults --hook
to PreCompact, which is the capture point it exists for; pass --hook to
override. It does NOT imply --all, so a PreCompact hook that wants the
whole session — which is the point of capturing there — passes it.
The block is printed, never delivered: stdout from a PreCompact hook
reaches the compaction summariser and not the session (dec-0023).
With no --transcript, stdin is read as a Claude Code hook payload if it
looks like one, and as prose otherwise.
Exit status is 0 whenever the command ran, including when it found
nothing: a hook must be able to tell that from dira being broken.dira distill
Review what sniff staged for you — one keystroke per entry: confirm or ignore.
dira distill - review what was staged for you, one keystroke per entry
usage:
dira distill review everything staged
dira distill --width 72 lay the cards out for 72 columns
dira distill -C path/to/repo review another repository's ledger
One card at a time, one byte to dispose of it. There is no prompt and no
newline: the key you press is the disposition, and the safety is `u`.
keys:
y stand behind it; the entry is promoted for extraction and stays staged
n not a decision; the capture is deleted and leaves nothing behind
e edit the because in $EDITOR, and stand behind it
u undo the last of those, byte for byte
q leave the rest staged and stop
flags:
--width N columns to lay a card out for; asks the terminal when unset
-C DIR run as if started in this directory
`y` does not accept the decision. A regular expression cannot know why a
road was refused, so confirming records that a human stood behind the
capture and hands it on for its reasoning; the entry stays staged until
something supplies the rejected alternative that makes it enforceable
(dec-0021, dec-0022, dec-0025).
Each card shows only what its entry records. A capture from the regex
tier has no because, no alternative and no ADR, and the card renders
nothing in their place (dec-0019).
With stdin not a terminal it reads nothing, changes nothing and exits 0,
so a hook or a CI step that reaches for it cannot hang. An empty queue
costs one line. Exit status is 2 only for a mistyped flag.dira check
Refuse a plan that contradicts a settled decision, quoting the record back at you.
dira check - refuse a plan that contradicts a settled decision
usage:
dira check [flags] "<plan or idea>"
Matches the plan against this ledger's accepted decisions' rejected
alternatives, its rejected decisions, and its active constraints. The
matching is lexical and runs entirely in this binary: no model, no
network, and no agent is involved in reaching the exit code.
flags:
-C <dir> run as if started in this directory
-json write the verdict as JSON (schema/check.schema.json)
exit codes:
0 the plan contradicts nothing this ledger enforces
2 at least one cited conflict
1 dira's own error — an unreadable ledger, a bad flag
A caller must never treat 1 as a verdict: it means the check did not run.dira brief
Print the session brief: what is blocked, the current focus, what was decided recently.
dira brief - the session brief: what is blocked, what is being worked
towards, and what was decided recently.
usage:
dira brief [flags]
Ordered by what matters most: open blockers (open questions holding
something up), then current focus (active intents), then recent
decisions, then notes from the last week.
The output is capped at brief.max_tokens in .dira/config.toml, 1500 by
default (cst-0001). Over the cap, whole entries are dropped from the
bottom of that order — never cut mid-entry — and the brief names what
it left out. There is no flag to raise the cap: raising it means
superseding cst-0001, in writing.
flags:
-C <dir> run as if started in this directory
-context the agent-facing form, injected by the SessionStart hook
-chain include the parent ledgers this one derives from
-width <n> wrap at this column (default 80)
examples:
dira brief
dira brief --context --chain
exit codes:
0 the brief, however much of the ledger fitted in it
1 the ledger could not be read at all
2 usage errordira why
Print the chain behind an entry — what it arises from, and every alternative it refused.
dira why - print the chain behind an entry.
usage:
dira why <id|term>
The chain is what an entry arises from, the alternatives it refused
and why, anything that superseded it, and the goal or ADR it points at.
An id resolves to itself. Any other term matches entry titles and tags,
newest first; a term matching several entries lists them rather than
choosing one.
flags:
-C <dir> run as if started in this directory
-width <n> wrap the chain at this column (default 80)
examples:
dira why dec-0002
dira why daemon
exit codes:
0 the chain, or the candidates a term matched
1 nothing in the ledger matches
2 usage errordira map
Join the ledger to kazi's execution status at read time — what is planned, running, blocked, or done.
dira map - join the ledger to kazi's execution status.
usage:
dira map [flags]
Groups accepted decisions and active intents by their derives_from
parent, one level, with a roll-up per parent. An entry with no parent
appears under an explicit unparented group. Status is derived from
kazi at read time and never stored (dec-0004): when kazi cannot be
asked, the ledger-side buckets still render and one line names why
execution status is unavailable.
flags:
-C <dir> run as if started in this directory
--json emit the documented JSON shape instead of text
examples:
dira map
dira map --json
exit codes:
0 the map, however kazi answered
1 the ledger could not be read
2 usage errordira supersede
Retire an entry in favour of the one that replaces it.
dira supersede - retire an entry in favour of the one that replaces it
usage:
dira supersede <id> --with <id> [flags]
Writes both sides of the supersession: the replacing entry gains a
`supersedes` edge, and the replaced entry's state becomes `superseded`.
`dira check` stops citing the retired entry and reports a match against
it as a redirect to its replacement instead.
flags:
--with ID the entry that replaces it; required
--note TEXT one line on why the replacement happened
-C DIR run as if started in this directory
Only a decision or a constraint can be superseded: the schema gives no
other kind a `superseded` state. An entry in another ledger cannot be
superseded from here — dira never writes upward (cst-0003).
The two writes are not atomic and cannot be: dira's storage interface
has no transaction, because none exists over the GitHub Contents API.
The edge is written first, so an interruption leaves the retired entry
still enforced rather than silently unenforced. Running the same
command again finishes the job.
exit codes:
0 the supersession is recorded — this run wrote it, or it was already
2 the ledger refuses it: a rule in the record says no
1 dira did not get that far — an unusable command line, an entry
that is not there, an unreadable ledger, a write that failed
A caller may read 2 as "refused on policy", and only that: an entry in
a parent ledger, a kind the schema gives no superseded state, an entry
already replaced, a staged replacement. A bad flag, a missing entry and
a failed write are all 1, so 2 never means dira is broken. This is the
same split `dira check` makes, where 2 is a verdict about the plan and
never a mistake in the flags — across both commands 2 is the record's
answer and 1 is never a verdict.dira ui
Serve the ledger index and the decision pages on localhost, read-only.
dira ui - serve the ledger index and the decision pages on localhost.
usage:
dira ui [-C dir] [-addr 127.0.0.1:port]
Server-rendered from the binary:
/ the ledger index, grouped by the intent each entry serves
/e/<id> one entry: its chain, its ruling, and the roads it refused
/distill the staged queue: confirm, reject, or edit the because
Every page renders completely with JavaScript disabled, and no page fetches
anything from any host. It binds loopback only: a ledger reachable from
the LAN is a ledger published by accident (cst-0004).
flags:
-C <dir> run as if started in this directory
-addr <a:p> loopback address to bind (default 127.0.0.1:0)
examples:
dira ui
dira ui -addr 127.0.0.1:7777
exit codes:
0 the server stopped on ctrl-c
1 the ledger could not be read, or the port could not be bound
2 usage error, including a request to bind a non-loopback addressdira install-skill
Write dira's capture skill into ~/.claude for Claude Code to load.
dira install-skill - write dira's capture skill into ~/.claude for Claude Code to load
usage:
dira install-skill install into ~/.claude
dira install-skill --force replace a locally edited copy
dira install-skill --root DIR install somewhere else
Writes skills/dira/SKILL.md — dira's capture tier 2. Tier 1 is the
regular expression inside this binary; tier 2 is the session that has
the conversation in context, and this document is what tells it how to
turn a staged capture into an entry it can cite (dec-0003).
flags:
--root DIR install under DIR instead of ~/.claude
--force replace the installed file even if it was edited
It writes one file and only inside the root, and it reports which of
three things happened:
INSTALLED the file was written
UNCHANGED it was already exactly this document; nothing was written
REFUSED it exists, differs, and was left alone; pass --force
Exit status is 0 whenever the command ran, a refusal included: leaving
an edited file alone is this command working. Nothing else in dira
writes to ~/.claude, and this only does so when you run it.dira install-hooks
Merge dira's SessionStart / Stop / PreCompact hook registrations into a settings file.
dira install-hooks - merge dira's Claude Code hook registrations into a settings file
usage:
dira install-hooks install into ~/.claude/settings.json
dira install-hooks --local install into <cwd>/.claude/settings.local.json
dira install-hooks --dir DIR install somewhere else
dira install-hooks --uninstall remove exactly what a previous install added
Merges three hook registrations -- SessionStart, Stop, PreCompact -- into
a Claude Code settings file (hooks/settings.example.json documents the
exact commands). It never clobbers: an operator's own hooks and keys, and
even their formatting, survive byte-identically, and a second install is a
byte-level no-op. It never touches the committed project
.claude/settings.json -- in a public repo that would publish your own workflow.
flags:
--local target <dir>/.claude/settings.local.json instead of the user-level file
--dir DIR install under DIR instead of ~/.claude
--uninstall remove exactly what a previous install added
It writes one file and only inside the root, and it reports which of
three things happened:
INSTALLED the file was written
UNCHANGED it already held exactly this; nothing was written
REMOVED --uninstall removed dira's own spans (or the whole file, if
install created it and nothing else has touched it since)
Exit status is 0 whenever the command ran, including a refusal to touch
an entry a human has since edited -- that refusal is reported on stderr,
named, and is this command working rather than failing. Nothing else in
dira writes to ~/.claude, and this only does so when you run it.dira reindex
Rebuild the derived SQLite read cache from the entry files — the files always win.
dira reindex - rebuild the derived read cache from the entry filesdira import
Measure a directory of existing ADRs and offer to import or index them.
dira import - measure a directory of ADRs and offer to import or index them
usage:
dira import DIR measure DIR, print a report, ask before writing
dira import DIR --yes skip the prompt and confirm unconditionally
DIR is walked one level deep for *.md files (dec-0028). Each is measured for
whether it records a rejected alternative with a reason. If none do, the report
offers to index them instead — a manifest under .dira/cache/imports/, never a
ledger entry. If at least one does, confirming imports one staged decision per
reasoned document into .dira/entries/, for `dira distill` to dispose of
individually. A directory already imported is skipped on a repeat run.
flags:
--yes skip the confirmation prompt and confirm unconditionally
-C DIR run as if started in this directory (the ledger, not the scanned directory)dira version
Print the dira version this binary was built at.
dira version - print the dira versionMore
- The ledger, rendered — dira's own decisions, why-chains and rejected alternatives.
- docs/design.md — the founding design.
- schema/entry.schema.json — the entry schema.
- README — the full pitch.