dira

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 --help

The 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 commands

dira 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 error

dira 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 error

dira 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 error

dira 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 error

dira 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 address

dira 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 files

dira 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 version

More