English · Русский · 中文 · Deutsch · Français
Your plan is a file — .devtree/tree.yaml — that sits next to the code. It is versioned with the
code, reviewed in the pull request, and merged line by line. From it, devtree draws a
Mermaid diagram into TREE.md or straight into your README.md, and a
picture of its own into .svg — as a tree, or as a board. GitHub and GitLab render both natively.
No browser extension, no image to regenerate by hand, nothing to host.
A roadmap in a tracker drifts away from the branch it describes. A roadmap in a wiki is read once and never again. A roadmap in the repository is a file people already have habits for.
Cards, progress rolled up per milestone, and the branch, issue and owner behind each task. Light and dark, following the theme you are reading in.
The same plan in your terminal:
Storefront
◐ MVP (mvp) [4/13]
├─ ◐ Backend (backend) [3/7]
│ ├─ ◐ Authentication (auth) feat/auth #12 @ann [1/2]
│ │ ├─ ✔ Sessions and refresh tokens (sessions)
│ │ └─ ⛔ Password reset (password-reset)
│ ├─ ◐ API architecture (api) @ann [1/2]
│ │ ├─ ✔ Resources and pagination (resources)
│ │ └─ ☐ Versioning and error shape (versioning) #24
│ └─ ✔ Docker image (docker) @bob
└─ ◐ Frontend (frontend) [1/4]
├─ ✔ Framework: React or Vue (stack)
├─ ◐ Catalog pages (catalog) feat/catalog @ann
├─ ☐ Checkout (checkout) #51
└─ ☐ nginx and static hosting (nginx) @bob
█████░░░░░░░░░░░░░░░ 4/14
devtree also writes a Mermaid block, which GitHub and GitLab draw
natively with no image files at all — that is what an .md output gets. This repository's own is in
TREE.md.
Every picture devtree draws — the tree above, the board, the interactive page — comes out of one vocabulary of twenty components.
| Card a panel, with described lines when needed | Cluster a dashed box around a region | Callout a bordered note | Swimlane a labelled band |
| ColumnHead a board column heading | Stat one number, said loudly | Chip a small labelled tag | Badge a count |
| Pill a state, in a wash of its colour | Avatar an owner as initials | Milestone a point, not a span | Legend the key to a drawing |
| Bar progress along a line | Ring the same, in a circle | Sparkline which way it is going | Timeline an axis with ticks |
| Divider a hairline | Connector a rounded elbow | Arrow a connector with a direction | Brace these belong together |
A component knows nothing about development plans. There is no status here and no task: a Pill
takes a colour and a label, and the renderer above decides that "blocked" is red. That is what lets
the illustrations in the documentation and the diagrams in your repository come out of one library
rather than two.
The gallery moves because movement is its subject — the rings and the sparkline draw themselves
on, the meters fill, the count arrives. Your plan diagrams stay restrained on purpose: there, motion
is spent on the four things worth noticing — the live path, work in flight, work blocked, progress
gained — and everything else holds still. Nothing moves at all under prefers-reduced-motion, or on
paper.
Can you click it? Not here, and not because of a shortcut we took: GitHub serves a repository's
SVG as an image under default-src 'none'; sandbox, so it gets no script, no navigation and not
even a hover. The same is true of its Mermaid, which ignores click. Where clicking does work is
the HTML export — name an output .html and every task becomes a link to its pull request, issue or
branch, with filters that need no JavaScript — and in the editor, where the drawing is the
editing surface.
p := theme.Palette()
parts.Card(&b, box, p, parts.CardStyle{
Accent: theme.Blocked,
Title: "Authentication",
Meta: "feat/auth · ann · #12",
Glyph: "lock-circle",
Motion: draw.ClassPulse, // blocked work breathes; settled work does not
Trailer: func(b *strings.Builder, r draw.Rect) {
parts.Bar(b, r, p, 3, 7, theme.Done, draw.ClassGrow)
},
})They are placed with a Rect that splits rather than with four numbers recomputed at every use:
head, body := card.SplitTop(34) // a band off the top, and what is left
inner := body.InsetXY(12, 8)
cols := inner.Columns(3, 10) // three columns with a gap between themThe gallery is generated by walking the component list, so a component added without a demo fails the build rather than quietly going undocumented. More: docs/components.md.
brew install SergeyLubivui-dev/tap/devtree # macOS and Linux
go install github.com/SergeyLubivui-dev/devtree@latestPrebuilt binaries for Linux, macOS, and Windows are attached to every release, with checksums. Or run it without installing anything:
docker run --rm -v "$PWD:/work" ghcr.io/sergeylubivui-dev/devtree renderBuilding from source needs Go 1.22 and nothing else. Details, and a shell alias that makes the container disappear into the background: docs/container.md.
cd your-project
devtree init --project "Storefront" --repo https://github.com/acme/storefront --hook --action
devtree add "Backend" -p mvp -s wip
devtree add "Authentication" -p backend -b feat/auth -i 12 -o ann -s wip
devtree add "Docker image" -p backend -o bob
devtree add "Frontend" -p mvp -s wip
devtree add "Checkout" -p frontend -i 51
devtree ls # the tree, in your terminal
devtree done docker-image
git add . && git commit -m "feat: docker" # the hook refreshes the diagram for youIDs are derived from titles — "Docker image" becomes docker-image, and non-Latin titles are
transliterated so the ID stays typeable. Pass --id when you want to choose one yourself.
A longer walk through the same ground: docs/getting-started.md.
devtree serve --open # http://127.0.0.1:9312
A real capture of the page, in both themes: the editor is dark by default and follows your system setting.
The plan on the left, drawn on the right, live. The drawing is the editing surface — every card
carries the id of the task it draws, so clicking one opens it and hovering one offers a + that
adds work underneath it. Five views behind the dropdown in the header — tree, board, page, Mermaid,
YAML — rendered on demand, with nothing written to disk until you press Write outputs.
A second section lists the documents: every destination in outputs, each showing what its name
says it holds — tree or board, light or dark, page or Mermaid block. Naming a new one there is how
you add an output; the name is what decides the drawing.
It binds to the loopback interface, stores nothing of its own, and every change lands in the same
.devtree/tree.yaml the command line writes: keep the editor in one window and a terminal in the
other, and neither loses an edit. More: docs/editor.md.
Short, real things people do with a plan. Copy one and change the words.
Start a feature, finish a feature. The task and the branch are named together, so anyone reading the diagram knows where the code is:
devtree add "Search filters" -p mvp -b feat/search -i 214 -o you -s wip
git switch -c feat/search
# ...write the code...
devtree done search-filters
git commit -am "feat: search filters" # the hook refreshes the diagramBreak something big into something doable. Parents count their children automatically, so the milestone reports progress you never have to update by hand:
devtree add "Billing" -s wip
devtree add "Invoices" -p billing
devtree add "Refunds" -p billing
devtree add "Dunning" -p billing -s blocked -n "needs the payments API"
devtree ls◐ Billing (billing) [0/3]
├─ ☐ Invoices (invoices)
├─ ☐ Refunds (refunds)
└─ ⛔ Dunning (dunning)
Pick up a ticket. Give it the issue number and it becomes a link in the table under the diagram:
devtree add "Fix timezone drift" -i 512 -o ann -s wip --tags bugPark what you cannot finish. A blocked task with no note is the one thing check complains
about, because "blocked" without a reason is a task nobody can pick up:
devtree set password-reset -s blocked -n "waiting on the SMTP contract"
devtree board -s blockedMonday morning, in three commands:
devtree board # what is in flight, what is stuck, what is waiting
devtree ls -s blocked # only the branches that need a decision
devtree check # anything marked done that still has open work under it?Two people, two branches, one plan. Both add tasks, both commit, and the merge is boring — the
node list is flat and .gitattributes says merge=union. If two branches happen to pick the same
ID, devtree check fails on the spot with the duplicate named.
The tree says how the work is organized. The board says what state it is in this morning — same file, different question:
devtree boardStorefront
☐ not started · 3
Versioning and error shape API architecture #24
Checkout Frontend #51
nginx and static hosting Frontend @bob
◐ in progress · 1
Catalog pages Frontend feat/catalog @ann
⛔ blocked · 1
Password reset Authentication — waiting on the SMTP account
✔ done · 4
Sessions and refresh tokens Authentication !31
Resources and pagination API architecture
Docker image Backend !40 @bob
Framework: React or Vue Frontend — React — the team already knows it
█████░░░░░░░░░░░░░░░ 4/14
Only leaves appear — a milestone is a container, not a card — and each task carries its milestone as
a breadcrumb. Name an output board.svg and the same thing is drawn as columns:
More: docs/board.md.
A plan that keeps every task ever completed stops being a plan and becomes a log. Two commands handle that from opposite ends:
devtree archive # what is finished and could move
devtree archive --all # move it into .devtree/archive.yaml
devtree restore v1 # bring a branch of it back
devtree sync # tasks whose branch git has already merged
devtree sync --apply # mark them doneNothing moves until you say so, and a node qualifies only when its whole subtree is done or dropped,
so live work can never leave with the milestone above it. sync proposes rather than acts, because
git knows which branches were merged but not which of them were merged finished.
More: docs/finished-work.md.
| Command | What it does |
|---|---|
init [--project N] [--repo URL] [--outputs F] [--hook] [--action] [--empty] |
Creates .devtree/tree.yaml, .gitattributes, and the first diagram |
add "Title" [-p ID] [-s STATUS] [-b BRANCH] [-i N] [--pr N] [-o WHO] [--tags a,b] [-n NOTE] [--id ID] |
Adds a task |
set ID [--title T] [-s ...] [-p ...] [...] |
Changes fields; only the flags you pass are touched |
done ID [ID...] |
Marks tasks done |
mv ID PARENT|root |
Re-parents a task |
rm ID [--cascade] |
Deletes a task; without --cascade its children move up to its parent |
ls [-s STATUS] |
Prints the tree in the terminal |
board [-s STATUS] |
Prints the work grouped by status |
open ID [--issue|--pr|--branch] [--print] |
Opens what a task points at |
archive [ID...] [--all] [--list] |
Moves finished branches of the plan into the archive |
restore ID [ID...] |
Brings archived work back |
sync [--apply] |
Closes tasks whose branch git has already merged |
history [--limit N] |
Reads past versions of the plan out of git and shows how far along it was |
serve [--port N] [--host H] [--open] |
Opens the local editor on 127.0.0.1 |
render [--file F] [--quiet] |
Regenerates every output |
check [--strict] |
Validates the plan — for CI and hooks |
install hook|action|gitlab|all |
Installs the pre-commit hook, the GitHub Action, or the GitLab job |
outputs |
Prints the files the diagram is written to |
Flags may come before or after the title, so both of these do the same thing:
devtree add "Authentication" -p mvp -s wip
devtree add -p mvp -s wip "Authentication"ls and board take the same four filters, and they combine:
devtree ls -s blocked # one status
devtree board -o ann # one person's work
devtree ls --tag billing,payments # any of these tags
devtree board --root mvp # one branch of the planrender takes --root too, so a milestone can have a picture of its own:
devtree render --root mvp --file docs/mvp.svgAnd when you want the thing itself rather than the plan:
devtree open authentication # its pull request, issue, or branch
devtree open authentication --print # print the URL instead of opening itThe canonical spelling is what lands in the file, whichever shorthand you type.
The plan lists its own output files, and the name decides the drawing:
| File name | What gets written |
|---|---|
TREE.md, README.md |
the Mermaid block, between <!-- devtree:begin --> and <!-- devtree:end --> markers |
docs/tree.svg |
devtree's own drawing of the tree, light palette |
docs/tree-dark.svg |
the same, dark palette |
docs/board.svg |
the board |
docs/plan.html |
a page you can click: links, filters, tooltips |
outputs: "README.md, docs/tree.svg, docs/tree-dark.svg, docs/board.svg, docs/plan.html"Everything outside the markers is left exactly as you wrote it, and re-rendering an unchanged plan
rewrites nothing at all. Point a <picture> at a light and dark pair and GitHub switches it with the
reader's theme.
Three things in the drawing move, and each one is information: a dash travels down the edges leading into work in progress, a glyph turns while a task is in flight and breathes while it is blocked, and progress bars grow in once. Readers who ask their system for less motion — and anyone printing — get a still picture.
File naming rules, palettes, the animation vocabulary, and the content security policy all of it has to survive: docs/svg-output.md.
Neither backend can carry a link: GitHub's Mermaid ignores click, and an SVG served as an image is
sandboxed. So there is a third one. Name an output .html and devtree writes a page where every
task that points somewhere is a link to its pull request, issue, or branch — with a tree view, a
board view, filters by status, owner and tag, and notes as tooltips.
It is one self-contained file with no script, no fonts and no images: the whole interface is CSS
built on :has(). Open it from a checkout, drop it on a shared drive, or point GitHub Pages at it.
This repository's own is docs/plan.html — see
docs/html-export.md.
devtree install hook # validate and re-render before every commit
devtree install action # fail a pull request whose diagram is out of dateThe hook keeps your own commits honest; the Action keeps everyone else's honest without requiring
them to install anything. A teammate without devtree is never blocked — the hook notices the binary
is missing and steps aside. On GitLab, devtree install gitlab writes a job that runs the published
container, so the pipeline needs no toolchain at all. Line by line:
docs/automation.md.
The plan has been in the repository all along, so its own past is already recorded. Nothing had to be tracked to read it back:
devtree historyStorefront — 3 revision(s) of the plan
2026-08-14 ░░░░░░░░░░░░░░░░░░░░ 0/2
2026-08-15 ██████░░░░░░░░░░░░░░ 1/3 +1 done +1 planned
2026-08-16 ████████░░░░░░░░░░░░ 2/5 +1 done +2 planned
2 of 5 done, up 2 since 2026-08-14
☐ 2 not started ◐ 1 in progress ✔ 2 done
This page is the tour. docs/ is the detail underneath it.
| Getting started | A first plan, from an empty repository to a diagram in the README |
| The file format | Every field, every rule, and what the parser refuses |
| The board | Working by status instead of by structure |
| Finished work | Archiving, restoring, closing tasks from merged branches |
| Automation | The pre-commit hook and the GitHub Action, line by line |
| SVG output | File naming, palettes, the animation vocabulary, the CSP it survives |
| The editor | The local editor, the drawing you can edit, and the motion in it |
| Drawing components | The twenty components every diagram is assembled from |
| The container | Running devtree without installing anything |
| Architecture | The layering, the rules that hold it, and how it is tested |
- The storage format is a strict subset of YAML — a flat list of scalar fields. Anchors, multi-line block scalars, and nested mappings are rejected, with the line number.
- Nothing rendered into a README is clickable: GitHub's Mermaid ignores
clickdirectives, and an SVG served as an image is sandboxed. Links live in the collapsed table under the Mermaid block. - Text in the SVG output is measured by estimate rather than by font metrics — shipping real metrics would mean shipping a font. Cards are sized a few pixels generously to compensate.
- Very wide trees (hundreds of nodes) render slowly in the browser. Split them across several output
files with
--outputs.
Rendered from .devtree/tree.yaml in this repository, on every push:
The same plan as a board — docs/assets/board.svg — as a page you can click — docs/plan.html — and as a Mermaid block, in TREE.md. One file, five renderings, none of them maintained by hand.
Issues and pull requests are welcome. CONTRIBUTING.md covers the checks CI runs, which package new code belongs in, and the two rules the layering depends on.
MIT © SergeyLubivui-dev
The vector glyphs in internal/icons are vendored from Reicon, also MIT —
see NOTICE.