dandori (dori)
A local-first, git-syncable ledger of work items and an activity journal.
Agents ingest from Jira, Vikunja, Forgejo, or wherever, and stay responsible
for freshness. dandori stores, dedups, and queries, as a library
(from dandori import Ledger) and a thin CLI (dori).
Storage is items/<id>.md (YAML frontmatter + markdown body) and an
append-only journal/<host>-YYYY-MM.jsonl in a plain git repo, synced with
git push/pull. There is no daemon and no credential store.
Install
uv tool install dandori-ledger
export DORI_DIR=~/dandori
dori init
--dir after the subcommand overrides $DORI_DIR. Every command except
init and host refuses a dir with no config.yaml, and every command
takes --json for {"schema_version": 1, "data": ...}.
Example
$ echo '[{"ref": "forgejo:tsugite#868", "title": "Mobile chat density", "status": "ready", "type": "bug", "priority": 2, "tags": ["tsugite", "ui"]}]' \
| dori upsert --stdin-json --source forgejo --actor alice
d-e43b31 Mobile chat density (bug, p2) [tsugite]
$ dori claim d-e43b31 --actor laptop-cc
claimed d-e43b31 for laptop-cc
$ dori log "root cause is PaneView.svelte:77, PR up as tsugite#871" --ref d-e43b31 --actor laptop-cc
logged: root cause is PaneView.svelte:77, PR up as tsugite#871
$ dori status
sources: forgejo 1s ago (alice)
OVERDUE (0)
IN FLIGHT (1)
d-e43b31 Mobile chat density (bug, p2) [tsugite] claimed laptop-cc, 1s
READY (0)
WAITING (0)
$ dori update d-e43b31 --status done --log "PR merged" --actor laptop-cc
d-e43b31 Mobile chat density (bug, p2) [tsugite] claimed laptop-cc, 1s
logged: PR merged
$ dori release d-e43b31 --actor laptop-cc
released d-e43b31
$ dori sync
synced with origin: pulled 0 items, pushed 0 items
$ dori show d-e43b31
d-e43b31 Mobile chat density (bug, p2) [tsugite]
tags: tsugite, ui
## Description
...
journal:
2026-09-15T03:42:27Z alice [create] created: Mobile chat density
2026-09-15T03:42:27Z laptop-cc [claim] claimed by laptop-cc
2026-09-15T03:42:27Z laptop-cc [log] root cause is PaneView.svelte:77, PR up as tsugite#871
2026-09-15T03:42:28Z laptop-cc [status] status: ready -> done
2026-09-15T03:42:28Z laptop-cc [log] PR merged
2026-09-15T03:42:28Z laptop-cc [release] released by laptop-cc
Items
refsidentify an item in external systems (forgejo:tsugite#868,jira:ABC-123). A ref belongs to at most one item, re-upserting a ref updates that item in place, and--refis repeatable.linksare annotations (pr:...,commit:...) and may repeat across items.upsert --linksmerges them in.- Every ref and link prefix must be registered:
dori prefix add|list.initseedsvikunja,forgejo,jira,commit,pr. titleand description belong to the item'ssource, set on create and taken over by a--forceupsert. An upsert from another source keeps them and prints a note.projectis derived from the cwd's git remote on create.--project Xsets it onupsertorupdate,update --no-projectclears it, andupsert --no-projectskips the derivation.listandstatusfilter with--project(-p), and-p .uses the cwd's derived project.update --description TEXTor--description-file PATH(-for stdin) replaces the description.--status ideamarks a thought with no commitment. Itshiddensection keeps it out ofstatusuntil someone claims it.--due YYYY-MM-DDshows inlistandshowand feeds the OVERDUE section, and--due ""clears it.dori split <parent> "child" ...createspart-ofchildren andneeds:deps on the parent. Openblocks:orneeds:deps derive[blocked], never a stored status, andlist/statusnest children under their parent.dori log "msg" --ref <id-or-ref> --actor Aappends a journal line against the item, and--refis required.--log MSGonupdate,claim, andreleasejournals one with the transition.showprints an item with its journal lines andguidea brief for an agent new to the ledger.doctorreports duplicate refs, expired claims, dangling deps, unregistered prefixes and statuses, stale items, string-valued tags, and log entries whose ref is not an item id. Duplicate refs, dangling deps, and string-valued tags exit 1. Everything else is informational.
Statuses
config.yaml's statuses map defines the statuses. dori init seeds it
with five, each carrying three flags:
statuses:
idea: {section: hidden, stale_days: null, terminal: false}
ready: {section: ready, stale_days: null, terminal: false}
waiting: {section: waiting, stale_days: null, terminal: false}
parked: {section: hidden, stale_days: null, terminal: false}
done: {section: hidden, stale_days: null, terminal: true}
default_status: ready
sectionis where an unclaimed item with that status shows indori status. Any name other thanhiddenbecomes its own section, anddori status --jsonlists them undersections.stale_days(int or null) is how many days without an update beforedoctorreports an item as stale.terminalcounts as done in a parent's[n/m done]progress, and a terminal dep no longer blocks its dependents.default_statusapplies toupsertwithout--statusand todori split's children.
Rename, add, or remove statuses in config.yaml. Writes refuse an
unregistered status, or an unregistered default_status, and list the
registered ones. blocked is reserved, since it is derived from open
blocks:/needs: deps.
A claimed item shows under IN FLIGHT whatever its status, unless the status is terminal. Releasing it returns it to its status's section. Any write can set any registered status.
A ledger created before statuses moved into config.yaml has no
statuses map. Until the block above is added, list, status, doctor,
guide, and any write that sets a status fail with an error.
Claims and sync
claim and release set or clear claimed_by and claimed_at and leave
status alone. dori list --claimed shows claimed items. Claims
expire after 4 hours. claim and release sync eagerly. When the data
dir is its own git repo with a reachable origin, they commit pending
writes, pull with rebase, then write, commit, and push. A claim that loses
the push race fails with claim lost to <actor>. Otherwise they stay
local-only and warn. sync.eager_claims: false in config.yaml turns this
off and --no-sync skips it for one call. On other writes --sync runs
dori sync afterwards; on claim/release it forces the eager pull and push.
dori sync commits, pulls with rebase, and pushes. A conflicting pull is
aborted and reported with the file names. Resolve it with git and rerun.
Hosts and actors
One machine is one host_id, the sole writer of its journal files. It is
stored at ~/.config/dandori/host and derived from the hostname on first
run. dori host shows it, dori host set X changes it, and $DANDORI_HOST
overrides it for one process. Each concurrent agent on a machine passes its
own --actor, and claims conflict on actor.
Claude Code plugin
The repo is a plugin with two hooks. SessionStart prints dori guide and
in-flight items into context. Stop refuses to
end a turn while an item claimed on this host has nothing logged since the
claim. Both need dori on PATH and DORI_DIR set. The repo is its own
marketplace:
/plugin marketplace add <this repo's git url>
/plugin install dandori@dandori
To try it from a checkout first, claude --plugin-dir /path/to/dandori.
fzf board
contrib/dori-fzf is an interactive board over the dori CLI. Requires fzf. Run it from the
ledger directory or set DORI_DIR; DORI_ACTOR sets the actor for writes
(default $USER). Creating items with ctrl-n needs a registered capture
prefix: dori prefix add adhoc --kind ref. Keybinds are listed in the header.
Publishing
Before pushing to a public remote, run the maintainer's privacy scan (kept outside this repo).
Metadata
Release files for dandori-ledger 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| dandori_ledger-0.5.0.tar.gz | 62.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dandori_ledger-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 107.6 kB
Release files / dandori_ledger-0.5.0.tar.gz
| Download URL | dandori_ledger-0.5.0.tar.gz |
|---|---|
| Size | 62.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2d1fd8b148e28a55c12418c16e30ca68d8a957aab48a970a5fb0ea74ef6ada5e
|
|
BLAKE2b-256 checksum How to use checksums |
7c3d948ba49a22ee96c28d1af0c454f76ab55ed810c21e9d8d66d71ecb5be8d7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logRelease files / dandori_ledger-0.5.0-py3-none-any.whl
| Download URL | dandori_ledger-0.5.0-py3-none-any.whl |
|---|---|
| Size | 45.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
54a2b4b8eb1bba60f86a14954a725138c9e9459377b66fc07ce4335fcf0d3be0
|
|
BLAKE2b-256 checksum How to use checksums |
baca4e7bae03c277c9ff796e847be5162b120ed581d39c6c06594b3385241270
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logSecurity for dandori-ledger 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for dandori_ledger-0.5.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.