busiless: A Simple Task Tracking and Auto-Prioritization Tool
For the past few months I've been regularly using a custom task tracking tool called busiless. I'm very happy with the tool: it's tiny and unobtrusive, integrates seemlessly into any code repository, and best of all runs entirely locally. After a few months of testing and polishing it's finally time to show it off to the world.
Installing busiless is described in the link below, as well as all usage information in detail. The rest of this post will be a simple, high level overview.
Defining Tasks
In busiless each task is simply a text file, be it markdown, plaintext, gemtext, or any other text. A task is uniquely identified by its absolute path relative to some root directory, which is specified using either a command-line argument or the BUSILESS_ROOT environment variable.
For example, given `BUSILESS_ROOT=~/src/project/tasks`, a task file at `~/src/project/tasks/bugs/config-file-panic.txt` would have the ID `/bugs/config-file-panic.txt`.
A task file must have a front matter section at the top of its file, with at least the `type` field set to `task`. This allows non-task files, such as images or other supporting documentation, to co-exist within the tasks root.
Our example `/bugs/config-file-panic.txt` task might look like this:
--- type: task --- The process panics when the path given by the --config-file flag doesn't exist.
Task files can exist at any level of the heirarchy relative to the root, even within the root directory itself.
Do You Like DAGs?
Defining single tasks is fine, but where busiless really shines is in allowing you to specify dependencies. Each task can list other tasks it depends on using the `after` field in its front matter. For example, let's say there's a task at `/improve-config/default-file.txt` which looks like this:
--- type: task after: - /bugs/config-file-panic.txt --- When --config-file is not given then a sane default configuration file should be used.
In our example we want to fix the panicking configuration handling before we go extending it with new features, and so we mark the feature task as being dependent on the bug.
The `after` field accepts an array, so a task can depend on many other tasks. Dependent tasks can be identified using their absolute path relative to the task directory root, or their path relative to the parent task file.
Dependent tasks can also be identified using a single-star glob pattern like `/bugs/*`, which matches all tasks within a directory, and double-star glob patterns like `/bugs/**` which match all tasks within a directory recursively. This gives a lot of freedom in how dependencies are managed.
A complex example might look like:
--- type: task after: - /bugs/** # Fix all bugs before this task - ./sibling.txt # Depend on a sibling in this same directory - ../parent.txt # Depend on a task in the parent directory - /epic/some-other-task.txt # Depend on some task in other sub-directory --- Blah blah blah
Using the dependencies specified by `after`, the set of all possible tasks forms a directed-acyclic graph (DAG). A DAG is, in essence, a tree structure where each node can have more than one parent. With busiless the parents of a task are all those which depend on it via a pattern in their `after` field.
busiless ships with the ability to visualize this dependency graph. By default the `visualize` sub-command will visualize the DAG comprising all tasks found by recursively searching the task root directory, but the `--pattern` flag can be given to only include tasks matching the pattern and those which they depend on.
For a real world example, this DAG was generated from the tasks of Micropelago's Isle project by doing:
busiless visualize --pattern /release/0.0.5.md > release-example.svg
An example of a complex dependency DAG
There's a lot going on, but the high-level summary which can be seen in that graph is that:
- release 0.0.5 is targeting two separate features ("s3fs-fuse" and "remove-host", whatever those might be)
- Those features ultimately depend on NATS pubsub being in place...
- ...which itself depends on firewall management.
Visualizing the DAG can also be done using the `serve` sub-command, which works exactly like `visualize` except that it serves a local HTTP endpoint which will automatically reflect changes to task files. In the future `serve` will be augmented with other niceities, like linking to rendered versions of the task files.
Prioritization
We've seen how busiless let's us define our tasks and the dependencies between them, and to visualize these dependencies in a graph. That graph can then be used to determine which tasks to prioritize: only "leaf" tasks, that is those without children, can be worked on, so pick one of those.
But reading the graph can be a bit chaotic, so busiless provides the `prioritize` sub-command:
busiless prioritize --pattern /release/0.0.5.md # 0) /firewall/daemon-config.md (320) # 1) /firewall/linux.md (160) # 2) /firewall/windows.md (160)
`prioritize` ouputs only the leaf tasks, which can be worked on immediately because they have no dependencies, ordered by their total weight. The total weight of each task is calculated as being its individual weight plus the total weight of the tasks depending on it. By ordering in this way we can prioritize tasks which, once complete, will unlock the most number of depending tasks. When working solo this might not be a huge distinction, but when attempting to parallelize a workload across multiple people it can be a huge difference.
The individual weight of a task defaults to 10, but can be overwritten by setting the `weight` field in the front matter. The units of this number are up to you and your project; the weight could correspond to the expected time to complete the task, or how important the task is, or how fun the task will be.
Completing Tasks
There are two ways to mark tasks as being completed: you can either delete the file, or set the `status` field to `done` in the front matter.
Deleting the file is certainly easier, but it will cause issues if some other task is still marked as depending on the deleted one. Marking the `status` as `done` doesn't have this issue, but it does leave behind a file which you may or may not care about anymore.
You can use the `check` sub-command to check whether or not deleting a task has caused some other task to have a missing dependency.
Working With Local Files is Great, We Should Do It More
I have been actively using busiless for the past few months and it's honestly been a dream. Not having to deal with some bloated web-interface with authentication and upsells and downtime is wonderful, and more than that I can use my own text editor (which I am extremely effective with) to write and manage tasks. I keep task files inside the project they apply to, so as I'm developing and an idea comes up I can just pop open a new file, drop it in the tasks root within the project (usually in something like a `triage` sub-directory, meaning I'll sort it out later), and continue on without changing windows or breaking flow.
Given this usage, the tasks integrate perfectly with source control too. I can even change and modify tasks within git branches, and since they are just text files any conflicts will be automatically dealt with by the normal git workflow. Anyone else working on the codebase has the tasks on-hand and automatically updated along with the rest of the code.
It's a drum that's been beaten for years, but we as an industry are so in the habit of building out a "service" to solve every problem that there's a lot of low-hanging fruit (like task trackers) which can be easily addressed using just text files and a simple CLI. For non-developers perhaps the service is more accessible, but that could easily be addressed with better text editors and ways to sync files which aren't git. Dropbox will do in a pinch, and I suspect the better text editors do exist but I don't know about them because I have no reason to. In any case, I'll be using busiless going forward for personal projects, and will probably be using it in the background professionally to augment JIRA as well.
If you have any ideas for improvements to busiless, or just want to give me some feedback on it or anything else, please drop me a message at my contact page!
Hi! I'm available for remote contract work. You can learn more about me and my skillset by browsing around this site, then head over to my resume site to find my work history and professional contact form.
This site is a mirror of my gemini capsule. The equivalent gemini page can be found here, and you can learn more about gemini at my 🚀 What is Gemini? page.