Help us shape the future of Go! Take the 2026 Go Developer Survey

devtree

command module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 15, 2026 License: MIT Imports: 2 Imported by: 0

README ΒΆ

devtree

Tree-shaped development planning that lives inside your repository and draws itself in the GitHub viewport.

CI Go Reference Zero dependencies

One Go binary. No dependencies, no service, no account.

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 generates a Mermaid diagram into TREE.md or straight into your README.md, and GitHub and GitLab render it natively. No browser extension, no image to regenerate, nothing to host.


Why

A roadmap in a tracker drifts away from the branch it describes. A roadmap in a wiki is read once and never again. Put it in the repository and three things follow for free:

It gets reviewed A change to the plan shows up in the diff, next to the change to the code.
It gets merged The node list is flat, so two branches that each added a task merge cleanly.
It stays honest A pre-commit hook and a CI check refuse to let the diagram drift from the plan.

What it looks like

This is a real devtree diagram β€” GitHub draws it right here in the page:

flowchart TD
    classDef todo fill:#f6f8fa,stroke:#8c959f,color:#1f2328
    classDef in_progress fill:#fff8c5,stroke:#bf8700,color:#1f2328
    classDef blocked fill:#ffebe9,stroke:#cf222e,color:#1f2328
    classDef done fill:#dafbe1,stroke:#1a7f37,color:#1f2328
    classDef dropped fill:#eaeef2,stroke:#8c959f,color:#6e7781,stroke-dasharray:4 3
    n_mvp["◐ MVP<br/><i>1/6</i>"]:::in_progress
    n_mvp --> n_authentication
    n_authentication["◐ Authentication<br/><i>0/2 Β· feat/auth Β· #35;12 Β· @ann</i>"]:::in_progress
    n_authentication --> n_oauth_providers
    n_oauth_providers["◐ OAuth providers<br/><i>feat/oauth</i>"]:::in_progress
    n_authentication --> n_password_reset
    n_password_reset["β›” Password reset"]:::blocked
    n_mvp --> n_payments
    n_payments["◐ Payments<br/><i>1/2</i>"]:::in_progress
    n_payments --> n_stripe
    n_stripe["βœ” Stripe"]:::done
    n_payments --> n_apple_pay
    n_apple_pay["☐ Apple Pay<br/><i>#35;51</i>"]:::todo
    n_public_api_v2["☐ Public API v2<br/><i>0/1</i>"]:::todo
    n_public_api_v2 --> n_openapi_schema
    n_openapi_schema["☐ OpenAPI schema"]:::todo

☐ not started Β· ◐ in progress Β· β›” blocked Β· βœ” done Β· βœ– dropped

The same plan in the terminal:

Payment Gateway

◐ MVP  (mvp)  [1/6]
β”œβ”€ ◐ Authentication  (authentication)  feat/auth  #12  @ann  [0/2]
β”‚  β”œβ”€ ◐ OAuth providers  (oauth-providers)  feat/oauth
β”‚  └─ β›” Password reset  (password-reset)
└─ ◐ Payments  (payments)  [1/2]
   β”œβ”€ βœ” Stripe  (stripe)
   └─ ☐ Apple Pay  (apple-pay)  #51
☐ Public API v2  (public-api-v2)  [0/1]
└─ ☐ OpenAPI schema  (openapi-schema)

β–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘  1/9

Install

go install github.com/SergeyLubivui-dev/devtree@latest

Or build it from source β€” Go 1.22 or newer, nothing else:

git clone https://github.com/SergeyLubivui-dev/devtree.git
cd devtree
go test ./...
go build -o devtree .

Cross-compiling is the usual one-liner:

GOOS=darwin GOARCH=arm64 go build -o devtree .
GOOS=windows GOARCH=amd64 go build -o devtree.exe .

Quickstart

cd your-project

devtree init --project "Payment Gateway" --repo https://github.com/acme/pay --hook --action

devtree add "Authentication"  -p mvp -b feat/auth -i 12 -o ann -s wip
devtree add "OAuth providers" -p authentication -b feat/oauth
devtree add "Password reset"  -p authentication -s blocked -n "waiting on SMTP"

devtree ls                                   # the tree, in your terminal
devtree done oauth-providers
git add . && git commit -m "feat: oauth"     # the hook refreshes TREE.md for you

IDs are derived from titles β€” "OAuth providers" becomes oauth-providers, and non-Latin titles are transliterated so the ID stays typeable. Pass --id when you want to choose one yourself.


Commands

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 on a task; 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
render [--file F] [--quiet] Regenerates the diagram
check [--strict] Validates the plan β€” for CI and hooks
install hook|action|all Installs the pre-commit hook and the GitHub Action
outputs Prints the files the diagram is written to

Statuses are todo, in_progress, blocked, done, and dropped. Shorthand works everywhere a status is accepted: wip, b, d, x, ok, doing, cancel, and friends.

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"

The file format

# Development tree. Edit by hand or through the `devtree` command.
# After editing, run `devtree render` to refresh the diagram.
version: 1
project: "Payment Gateway"
repo: "https://github.com/acme/pay"
outputs: "TREE.md"
nodes:
  - id: "mvp"
    title: "MVP"
    status: "in_progress"
  - id: "authentication"
    title: "Authentication"
    status: "in_progress"
    parent: "mvp"
    branch: "feat/auth"
    issue: "12"
    owner: "ann"
  - id: "password-reset"
    title: "Password reset"
    status: "blocked"
    parent: "authentication"
    note: "waiting on SMTP"

The list is flat; the hierarchy comes from the parent field. That is a deliberate trade. Nesting would mean that adding a task rewrites an existing block β€” exactly the shape that makes two feature branches conflict. A flat list turns "add a task" into "append lines", so parallel branches merge cleanly, and init writes a merge=union rule into .gitattributes to finish the job. Worst case you end up with a duplicate ID, which devtree check catches on the spot.

Edit the file by hand whenever you like. The parser is strict and tells you the line number:

devtree: .devtree/tree.yaml: line 14: unknown node field "assignee"

Automation

Pre-commit hook β€” devtree install hook

Validates the plan, regenerates the diagram, and stages the result along with your commit. An existing hook is preserved as pre-commit.devtree-backup. Teammates who have not installed devtree are not blocked: the hook notices the binary is missing and steps aside.

GitHub Action β€” devtree install action

  • on pull_request: fails if the diagram is stale, so the author fixes it in their own diff;
  • on push to the default branch: refreshes the diagram and commits it.

Rendering into your README

devtree init --outputs "README.md"

The block is written between the <!-- devtree:begin --> and <!-- devtree:end --> markers, each on a line of its own. If the markers are not there yet, the block is appended once and updated in place from then on β€” so put the empty pair wherever you want the diagram to appear. Everything outside them is left exactly as you wrote it, and re-rendering an unchanged plan rewrites nothing at all.

Only markers that start their own line count. Prose that mentions them mid-sentence β€” the paragraph you are reading, for instance β€” is left alone.

Issue and pull request links are built from repo. If repo is not set, devtree falls back to relative links (../../issues/12), which GitHub resolves correctly for files in the repository root and which survive a fork or a move to another host.


How it is built

Four layers, and the dependencies only ever point inward:

main.go                  ~20 lines: parse nothing, print nothing, just wire and exit
└── internal/cli         flags, dispatch, and every line the user sees
    β”œβ”€β”€ internal/store   the strict YAML subset: parse, marshal, atomic save
    β”œβ”€β”€ internal/render  Mermaid, Markdown, ASCII β€” pure string functions
    β”œβ”€β”€ internal/scaffold  hook, workflow, and .gitattributes templates
    └── internal/tree    the domain: nodes, parents, statuses, validation

internal/tree imports nothing but the standard library. It does not know that a file format exists, and it certainly does not know about Mermaid β€” which is why the rules about what a valid plan is live in one place and stay there. Everything below the CLI is silent: no printing, no os.Exit, errors returned as values. App takes its working directory and its two writers as fields, so the test suite drives whole commands against a temporary directory and reads back exactly what a user would have seen.

Run the tests the usual way:

go test ./...
go vet ./...

Limitations

  • The storage format is a strict subset of YAML β€” a flat list of scalar fields. Anchors, multi-line block scalars, and nested mappings are not supported and will be rejected with a line number.
  • GitHub's Mermaid renderer ignores click directives, so a node in the diagram cannot itself be a link. The links live in the collapsed table underneath instead.
  • Very wide trees (hundreds of nodes) render slowly in the browser. Split them across several output files with --outputs.

devtree's own plan

Rendered by devtree, from this repository, on every push:

🌳 devtree

β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 8 / 16 tasks done

flowchart TD
    classDef todo fill:#f6f8fa,stroke:#8c959f,color:#1f2328
    classDef in_progress fill:#fff8c5,stroke:#bf8700,color:#1f2328
    classDef blocked fill:#ffebe9,stroke:#cf222e,color:#1f2328
    classDef done fill:#dafbe1,stroke:#1a7f37,color:#1f2328
    classDef dropped fill:#eaeef2,stroke:#8c959f,color:#6e7781,stroke-dasharray:4 3
    n_v0_1["βœ” v0.1 - first release<br/><i>6/6</i>"]:::done
    n_v0_1 --> n_domain
    n_domain["βœ” Domain model and validation"]:::done
    n_v0_1 --> n_format
    n_format["βœ” Strict YAML subset parser"]:::done
    n_v0_1 --> n_diagram
    n_diagram["βœ” Mermaid renderer"]:::done
    n_v0_1 --> n_cli
    n_cli["βœ” Command line"]:::done
    n_v0_1 --> n_automation
    n_automation["βœ” Pre-commit hook and GitHub Action"]:::done
    n_v0_1 --> n_tests
    n_tests["βœ” Test suite"]:::done
    n_v0_2["☐ v0.2 - sharper day-to-day use<br/><i>0/4</i>"]:::todo
    n_v0_2 --> n_filters
    n_filters["☐ Filter ls by owner and tag"]:::todo
    n_v0_2 --> n_focus
    n_focus["☐ Render one subtree with --root"]:::todo
    n_v0_2 --> n_open
    n_open["☐ devtree open ID - jump to the issue or PR"]:::todo
    n_v0_2 --> n_history
    n_history["☐ Progress history from git log"]:::todo
    n_distribution["☐ Distribution<br/><i>1/3</i>"]:::todo
    n_distribution --> n_binaries
    n_binaries["βœ” Prebuilt binaries on every tag"]:::done
    n_distribution --> n_homebrew
    n_homebrew["☐ Homebrew tap"]:::todo
    n_distribution --> n_gitlab
    n_gitlab["☐ GitLab CI template"]:::todo

☐ not started Β· ◐ in progress Β· β›” blocked Β· βœ” done Β· βœ– dropped

License

MIT Β© SergeyLubivui-dev

Documentation ΒΆ

Overview ΒΆ

Command devtree keeps a development plan inside the repository and renders it as a Mermaid diagram that GitHub and GitLab draw natively.

The plan lives in .devtree/tree.yaml: it is versioned with the code, reviewed in the pull request, and merged line by line. See README.md for the full tour, or run `devtree help`.

This file is deliberately thin. Everything it could do belongs in a package that can be tested without a process: the tree itself in internal/tree, the file format in internal/store, the output in internal/render, and the command line in internal/cli.

Directories ΒΆ

Path Synopsis
internal
cli
Package cli is the outermost layer: it parses arguments, calls the domain and storage packages, and owns every line printed to the terminal.
Package cli is the outermost layer: it parses arguments, calls the domain and storage packages, and owns every line printed to the terminal.
render
Package render turns a plan into text: a Markdown block for the repository, a Mermaid diagram GitHub draws natively, and an ASCII tree for the terminal.
Package render turns a plan into text: a Markdown block for the repository, a Mermaid diagram GitHub draws natively, and an ASCII tree for the terminal.
scaffold
Package scaffold writes the files that make a plan keep itself up to date: a pre-commit hook, a GitHub Actions workflow, and a .gitattributes rule.
Package scaffold writes the files that make a plan keep itself up to date: a pre-commit hook, a GitHub Actions workflow, and a .gitattributes rule.
store
Package store reads and writes .devtree/tree.yaml.
Package store reads and writes .devtree/tree.yaml.
tree
Package tree is the domain layer: the nodes of a development plan, the parent/child relationships between them, and the rules that keep those relationships a tree rather than a tangle.
Package tree is the domain layer: the nodes of a development plan, the parent/child relationships between them, and the rules that keep those relationships a tree rather than a tangle.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL